Monday, October 28, 2013

การตรวจสอบข้อมูลชนิด DateTime และ Numeric ด้วย IsDate() และ IsNumeric() ในภาษา C#

ไม่ได้อัพเดตสเปซเสียนานเลยครับ ช่วงนี้หน้าที่การงานรัดตัวเหลือเกินทั้งงานราษฎร์งานหลวง (มันเป็นข้อแก้ตัวที่ถึงแม้จะเอามารีไซเคิลใช้เรื่อยๆก็ยังดูดีนะครับ) สำหรับคราวนี้มีปัญหาเล็กๆแต่ก็น่ากวนใจสำหรับโปรแกรมเมอร์มือใหม่รวมถึงมือกลางเก่ากลางใหม่อย่างผมด้วย
ปัญหาที่ว่านี้ผมคิดว่าน่าจะเป็นปัญหาลำดับต้นๆปัญหาหนึ่งสำหรับนักพัฒนาที่เคยพัฒนาโปรแกรมด้วยภาษา VB มาก่อนและมาเริ่มต้นศึกษาการเขียนโปรแกรมด้วยภาษา C# ก็คือ การตรวจสอบชนิดของข้อมูล DateTime กับ Numeric ซึ่งใน VB จะมีฟังก์ชั่นสำหรับตรวจสอบชนิดของข้อมูลทั้งสองนี้และคืนค่ากลับมาเป็น Boolean (True/False) ฟังก์ชั่นที่กล่าวถึงนี้คือ IsDate() และ IsNumeric() ฟังก์ชั่นทั้งสองนี้มีใน VB มานานมากแล้วตั้งแต่สมัย VB4-VB5 นู่น (เก่ากว่านั้นผมไม่ทันครับ ;P) แต่…ใน C# ไม่ยักมี ดังนั้นเวลาที่เราต้องการแปลงชนิดข้อมูลไป-มาก็มักจะเกิดปัญหาบ่อยๆ จากการที่ไม่ได้ตรวจสอบชนิดของข้อมูลเสียก่อน
ที่ว่ามันกวนใจก็คือ เวลาจะแปลงชนิดข้อมูลแต่ละครั้งต้องมาเขียนตรวจสอบเหมือนเดิมๆทุกครั้ง บ่อยเข้าก็ชักจะหงุดหงิด เลยเกิดความคิดว่าน่าจะหาอะไรมาใช้แทนฟังก์ชั่นทั้งสองนั้นใน C# ได้มั่งนะ หาไปหามาก็ไปพบบทความของฝรั่งเค้า ซึ่งผมเห็นว่าน่าสนใจดี ที่เขียนชุดคำสั่งเลียนแบบการทำงานของ IsDate() และ IsNumeric() และผมก็ได้นำแนวคิดของเขาและโปรแกรมตัวอย่างมาลองเขียนและปรับปรุงนิดหน่อย และทดสอบดูแล้วเห็นว่าเข้าท่าดี ก็เลยเอามาแชร์กัน
สำหรับการเขียนฟังก์ชั่นเลียนแบบ IsDate() และ IsNumeric() นี้เรามีพระเอกอยู่หลายตัวครับ พระเอกของเราตัวแรกก็คือเมธอด Parse เมธอดนี้จะทำการแปลงค่า string ที่ส่งเข้ามาให้เป็นชนิดข้อมูลพื้นฐานของ .Net Framwork ซึ่งข้อมูลชนิดพื้นฐานทุกชนิดจะ implement เมธอดนี้ เช่น DateTime.Parse(args), Int32.Parse(args) เป็นต้น เมธอด Parse จะคืนค่าเป็นชนิดของข้อมูลที่เราต้องการหากค่า string ที่ส่งเข้ามานั้น มีรูปแบบที่ถูกต้องตามแบบของชนิดข้อมูลที่เราต้องการแปลง และจะโยน exception ออกมาถ้าหากรูปแบบไม่ถูกต้อง เช่น Int32.Parse(“1234”) จะคืนค่าออกมาเป็น 1234 แต่ Int32.Parse(“ABCD”) กรณีนี้จะเกิด exception ครับ ดังนั้นหลักการก็คือ เราดัก exception ด้วยการใส่บล็อก try {} catch{} เข้าไปที่ฟังก์ชั่นที่เราสร้างขึ้นนั่นเอง เมื่อเกิด exception ก็คืนค่าเป็น false กลับมา หากไม่เกิด exception ก็อนุมานว่าค่าที่ส่งเข้าไปนั้นถูกต้อง และคืนค่า true กลับมา มาดูโค้ดกันดีกว่าครับ โค้ดเราสั้นๆ ง่ายๆ ดังต่อไปนี้
IsDate()
public static bool IsDate(object argo) {
    try {
        DateTime dt = DateTime.Parse(argo.ToString());
        if (dt != DateTime.MinValue && dt != DateTime.MaxValue)
            return true;
        return false;
    } catch {
        return false;
    }
}
IsNumeric()
public static bool IsNumeric(object argo) {
    try {
        float result = float.Parse(argo.ToString());
        return true;
    } catch {
        return false;
    }
}
ฟังก์ชั่นด้านบนทั้งสองฟังก์ชั่น สามารถใช้ได้ใน .Net Framework ตั้งแต่ 1.1 ขึ้นไปเลย เพราะเมธอด Parse นี่มีมาในเวอร์ชั่น 1.1 แล้ว ส่วนผู้ที่ไม่ได้ใช้เฟรมเวอร์กเวอร์ชั่น 1.1 แล้ว และไม่คิดว่าจะกลับไปใช้อีก ก็สามารถใช้ความสามารถที่เพิ่มเติมมาในเฟรมเวอร์กเวอร์ชั่น 2.0 ด้วยการใช้เมธอด TryParse ครับ
เมธอด TryParse นี้เป็นการปรับปรุงให้ดีขึ้นกว่าเมธอด Parse ครับ เพราะมันจะไม่เกิด exception ในกรณีที่แปลงค่าไม่ได้ ซึ่งจะทำให้ประสิทธิภาพโดยรวมของระบบดีขึ้น ทำงานได้เร็วขึ้น เพราะไม่ต้องเสียทรัพยากรของระบบในการจัดการกับ exception เลย เมธอด TryParse นี้จะคืนค่ากลับเป็น Boolean โดยจะมีค่าเป็น false หากแปลงค่าไม่ได้ และเป็น true หากแปลงค่าได้สำเร็จ และในกรณีที่แปลงค่าได้สำเร็จ ค่าที่แปลงได้จะถูกคืนกลับมาในอาร์กิวเม้นต์ตัวที่สอง ที่เราใส่คีย์เวิร์ด out ไว้ข้างหน้านั่นเอง
โค้ดตัวอย่างในการใช้งานทั่วๆไปของเมธอด TryParse
namespace ConsoleApplication1 {
    class Program {
        static void Main(string[] args) {
            DateTime birthDate;
            string dateString = "11/05/1987 23:59:59";
            if (DateTime.TryParse(dateString, out birthDate)) {
                Console.WriteLine("Your birthdate is {0:dd/MM/yyyy hh:mm:ss tt}"birthDate);
            } else {
                Console.WriteLine("Your birthdate is not specified!!!");
            }
            Console.ReadKey();
        }
    }
}
ผลลัพธ์ : Your birthdate is 11/05/1987 11:59:59 PM

โค้ดตัวอย่างข้างล่างนี้เป็นการนำเมธอด TryParse มาใช้แทนเมธอด Parse ในฟังก์ชั่น IsDate() และ IsNumeric() ที่เราสร้างขึ้นเองครับ
IsDate()public static bool IsDate(object argo) {
    DateTime dt;
    if (DateTime.TryParse(argo.ToString(), out dt))
        return true;
    return false;
}
IsNumeric()public static bool IsNumeric(object argo) {
    float number;
    if (float.TryParse(argo.ToString(), out number))
        return true;
    return false;
}
เท่านี้เราก็ได้ฟังก์ชั่นตรวจสอบชนิดของข้อมูลมาใช้โดยไม่ต้องทิ้งความรู้สึกเดิมๆในตอนที่เขียนด้วย VB ไปแล้วครับ

ยังมีอีกวิธีหนึ่งที่ง่ายกว่าเขียนฟังก์ชั่นเลียนแบบการทำงานของ IsDate() และ IsNumeric() เองเสียอีกครับ นั่นคือการเรียกใช้ฟังก์ชั่นเหล่านี้ตรงๆ
ครับ!ไม่ผิดหรอก เราสามารถเรียกใช้ฟังก์ชั่น IsDate(), IsNumeric() และฟังก์ชั่นอื่นๆ ของ VB ได้โดยตรงจาก C# โดย
1. จากโปรเจกต์ของเรา Add Reference ไปยังแอสเซมบลี Microsoft.VisualBasic.dll
2. เพียงเท่านี้เราก็สามารถเข้าถึงเมธอดต่างๆของคลาส Microsoft.VisualBasic.Information ได้แล้วครับ รวมไปถึง IsDate() และ IsNumeric()
ตัวอย่าง:using Microsoft.VisualBasic;
.
.
.
DateTime? birthDate= (Information.IsDate(args) ? DateTime.Parse(args) : null);
โอ้! พระเจ้า! จอร์จ! มันง่ายอะไรอย่างนี้! แต่ว่า . . . ดูก่อน! ท่านผู้เจริญ! ข่าวร้ายครับ จากแหล่งข้อมูลอ้างอิงของเรา วิธีนี้ไม่เป็นที่แนะนำ เพราะว่า คลาสต่างๆในเนมสเปซ Microsoft.VisualBasic (ใน Microsoft.VisualBasic.dll) นั้น เขาทำขึ้นมาเพื่อให้มันเข้ากันได้กับโค้ดภาษา VB รุ่นเก่าๆ (VB4-VB6) เท่านั้นครับ  ดังนั้นในอนาคตอันไกล อาจจะไม่มีการสนับสนุนการเข้ากันได้กับโค้ดภาษา VB รุ่นเก่าๆเหล่านั้น (เมื่อมันกลายเป็นภาษารุ่นทวดของทวดของทวด) ก็เป็นได้ เราจึงควรจะใช้ฟังก์ชันใหม่ เมธอดใหม่ที่มีให้ใช้ในเฟรมเวอร์กรุ่นใหม่ๆจะดีกว่า

แหล่งข้อมูลอ้างอิง :

Thursday, October 10, 2013

location control in screen

I usually combine PointToScreen and PointToClient:

Point locationOnForm = control.FindForm().PointToClient(
    control.Parent.PointToScreen(control.Location));