Monthly Archives: กุมภาพันธ์ 2007

ตอน 32 Object และ collection initializes

ไปหน้าแรก | สารบัญ | Laploy.comระเบียนบทความ | บทความจากลาภลอย

เว็บไซต์นี้เป็นตัวอย่างเนื้อหาบางตอนในหนังสือ "เรียนรู้ด้วยตนเอง OOP C# ASP.NET" ครอบคลุม บทที่ 1 ถึงบทที่ 6 (ในหนังสือมี 21 บท) เนื้อหาใน Blog อาจอาจแตกต่างจากในหนังสือเพราะเป็นเนื้อหาที่ยังไม่ได้ตรวจแก้ขัดเกลา (edit)

กดที่นี่เพื่อดูรายละเอียดเนื้อหาในแต่ละบท

กดที่นี่เพื่อไปยังเว็บบอร์ด ถาม-ตอบ 

 

 

Object และ collection initializes

 

object initializes (ต่อไปจะเรียกย่อว่า OI) เป็นคุณสมบัติเริ่มมีใช้ใน C#3.0 ช่วยอำนวยความสะดวกในการกำหนดค่าเริ่มต้นให้แก่ object ทำให้เขียนโค้ดได้กระชับขึ้น สมมุติว่าเรามีคลาสดังต่อไปนี้

class Student
{
    public string name;
    public string address;
    int age;

    public int Age
    {
        get { return age; }
        set { age = value; }
    }
}

โค้ดที่เห็นข้างบนเป็นนิยามคลาสชื่อ Student เป็นคลาสธรรมดาไม่มีอะไรพิเศษ เมื่อสร้าง object จากคลาสนี้และกำหนดค่าเริ่มต้นให้แก่ object ใน C#2.0 เราจะใช้คำสั่งดังต่อไปนี้

class Program
{
    static void Main(string[] args)
    {
        Student myStudent = new Student();
        myStudent.name = "somchai";
        myStudent.address = "123 sukumvit road, Bangkok";
        myStudent.Age = 20;
    }
}

แต่คุณสมบัติ OI ใน C#3.0 ช่วยให้เราสามารถเขียนโค้ดอย่างนี้แทนได้

var myStudent = new Student(name = “somchai”,
address = "123 sukumvit road, Bangkok",
Age = 20);

นอกจากจะทำให้โค้ดสั้นลงแล้ว OI ยังช่วยเพิ่มความยืดหยุ่นในการเขียนโค้ดได้ ในโค้ดตัวอย่างนี้จะเห็นว่าเราไม่ต้องสร้าง constructor และทำ overload constructor หลายๆ แบบเพื่อรับพารามิเตอร์หลายๆ รูปแบบ เพราะ CI ทำงานได้ทั้งกับ field ที่เป็น public และ properties

นอกจาก C#3.0 จะมี object initializes แล้วยังมี collection initializes (ต่อไปจะเรียกย่อว่า CI) ด้วย หลักการและหน้าที่ของ CI ก็เหมือน OI แปลกกันที OI ทำงานกับ object เพียงตัวเดียวขณะที่ CI ใช้กับชุดหรือกลุ่มของ object โดยมี syntax ดังนี้

List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

สมมุติว่าเราต้องการสร้าง object collection จากคลาส Student สามารถทำได้ดังนี้

List<Customer> custList = new List<Customer> {
   new Customer
   {
     name = "somchai", address = "Bangkok",
   },
   new Customer
   {
      name = "malee", address = "Bangkok",
   },
   new Customer
   {
      name = "boonme", address = "Kanjanaburi",
   }
};

ข้อสรุปเรื่อง Object และ collection initializes
• OI อำนวยความสะดวกในการสร้างและกำหนดค่าเริ่มต้นให้ object
• CI อำนวยความสะดวกในการสร้างและกำหนดค่าเริ่มต้นให้ชุดของ object
• เริ่มมีใช้ใน C#3.0
• ช่วยให้การเขียนโค้ดยืนหยุ่นและกระชับขึ้น

 

Anonymous types

Anonymous types (ต่อไปจะเรียกย่อว่า AT) เป็นคุณสมบัติเริ่มมีใช้ใน C#3.0 ช่วยอำนวยความสะดวกในการสร้าง และกำหนดค่าเริ่มต้นให้แก่ object โดยไม่ต้องกำหนด type ทำให้การเขียนโค้ดยืดหยุ่นขึ้นจากตัวอย่างโค้ดในหัวข้อ object และ collection initializes เราสามารถสร้างและกำหนดค่าเริ่มต้นให้ object ที่สร้างจากคลาส Student โดยอาศัย object initializes ได้ดังนี้

var myStudent = new Student(name = “somchai”,
address = "123 sukumvit road, Bangkok",
Age = 20);

จากโค้ดข้างบน โปรดสังเกตว่าเรากำหนดให้ object myStudent มี type เป็น var ซึ่งเป็น Implicitly typed local variables (ดูรายละเอียดในหัวข้อก่อนหน้านี้) เมื่อคอมไพล์แล้ว myStudent จึงจะได้รับ type ที่แท้จริง (คือ Student) นับว่าช่วยให้การเขียนโค้ดยืดหยุ่นดี แต่ AT ก้าวหน้าไปอีกขึ้นหนึ่ง เพราะเมื่อใช้ AT เราสามารถสร้างและกำหนดค่าเริ่มต้นให้ object โดยเขียนโค้ดแบบนี้ได้

var myStudent = new(name = “somchai”,
address = "123 sukumvit road, Bangkok",
Age = 20);

โปรดสังเกตว่าโค้ดข้างบนเหมือนโค้ดก่อนหน้าทุกประการยกเว้นเราละชื่อ constructor ออกไป ซึ่งมีความหมายเท่ากับไม่ได้ระบุ type ที่ใช้ในการกำหนดค่าเริ่มต้น ดังนั้นเราจึงไม่รู้ว่า object myStudent มี type เป็นอะไรแน่ แต่โปรแกรมก็ทำงานได้โดยไม่ error เพราะ compile จะสร้าง type ให้เราขณะคอมไพล์ ซึ่งจะมีโค้ดเป็นดังนี้

public sealed class <Projection>f__0
{
    // Methods
    public <Projection>f__0();
    public override bool Equals(object);
    public override int GetHashCode();
    public override string ToString();
    // Properties
    public string Address { get; set; }
    public string Name { get; set; }
    // Fields
    private string _Address;
    private string _Name;
} 

Anonymous types
• ช่วยให้เราสร้าง object ได้โดยไม่ต้องระบุ type
• syntax คล้าย object initializes แต่ไม่ระบุ type
• มักใช้ร่วมกับ Implicitly typed local variables
• compiler จะสร้าง type ให้ขณะคอมไพล์ (late binding)

 

Implicitly typed arrays

Implicitly typed arrays (ต่อไปจะเรียกย่อว่า ITA) เป็นคุณสมบัติเริ่มมีใช้ใน C#3.0 ช่วยอำนวยความสะดวกในการประกาศ และกำหนดค่าเริ่มต้นให้แก่ array โดยไม่ต้องกำหนด type, ขนาด และ dimension ทำให้การเขียนโค้ดยืดหยุ่นขึ้น

เดิมทีใน C#2.0 เมื่อเราต้องการประกาศ และกำหนดค่าเริ่มต้นให้แก่ array จะใช้คำสั่งดังนี้

int[] arr = new int[] { 1, 2, 3, 4, 5 };

โค้ดบรรทัดบนทำหน้าที่ประกาศ array ชื่อ arr กำหนดให้มี data type เป็น int และกำหนดค่าเริ่มต้นให้เป็น 1 ถึง 5 ทำให้ได้ array แบบ dimension เดียวที่มี array element ห้าตัว แต่ใน C#3.0 เราสามารถเขียนโค้ดแบบนี้ได้

var[] arr = new[] { 1, 2, 3, 4, 5 };

เป็นการประกาศ array ชื่อ arr และกำหนดค่าเริ่มต้นให้เป็น 1 ถึง 5 ทำให้ได้ array แบบ dimension เดียวที่มี array element ห้าตัวเช่นเดียวกัน โปรดสังเกตว่าเราไม่ได้กำหนด data type แต่เราใช้ var ซึ่งเป็น Implicitly typed local variables แทน เมื่อคอมไพล์ arr จะมี data type เป็น int โดยถูกกำหนดโดยนัยจากข้อมูลที่เราให้เป็นค่าเริ่มต้นของมัน

 

ข้อสรุปเรื่อง Implicitly typed arrays
• ช่วยให้เราสร้าง array ได้โดยไม่ต้องระบุ type
• syntax คลายการนิยาม array ธรรมดาแต่ไม่ระบุ type
• มักใช้ร่วมกับ Implicitly typed local variables
• compiler จะกำหนด type ให้ขณะคอมไพล์

 

ที่ท่านอ่านจบไปในบทนี้คือหัวข้อสำคัญต่างๆ ที่เกี่ยวกับภาษา C# ระดับสูงและคุณสมบัติใหม่ๆ บางอย่าง ที่เพิ่มขึ้นใน C#3.0 บทต่อไปท่านจะได้เรียนวิธีเขียนโปรแกรมภาษา C# เพื่อสร้าง web application ก่อนที่จะเริ่มภาคปฏิบัติในบทที่ 7 ต่อไป

คำถามท้ายบทที่ 5

  1. เมื่อเรานิยามคลาส หากเราไม่ระบุ base class ภาษา C# จะถือว่า base class คืออะไร
  2. versioning คืออะไร
  3. assignment คืออะไร
  4. type casting แตกต่างจาก boxing และ unboxing อย่างไร
  5. การแปลง type ด้วยคำสั่ง as แตกต่างจากการทำ type casting อย่างไร
  6. method ต้องมี modifier เป็นแบบใดเราจึงจะสามารถ override ได้
  7. early binding คืออะไร
  8. late binding คืออะไร
  9. การทำ hiding ต้องใช้คำสั่งอะไร
  10. คลาสที่ไม่ใช่ Interface ซึ่งโค้ดภายในมีแต่ประกาศนิยามสมาชิก เรียกว่าอะไร
  11. หากไม่กำหนด modifier ให้ abstract method จะมีภาวะเป็นอะไร
  12. Interface มีขึ้นเพื่อสนับสนุนหลักการอะไร
  13. Interface มีประโยชน์อย่างไร
  14. อะไรคือ object collection
  15. IEnumerator คืออะไร
  16. หากจะใช้ IEnumerator เราต้องเตรียม method ใดไว้บ้าง
  17. Indexer คืออะไร
  18. Iterator คืออะไร
  19. Regex คืออะไร
  20. generic มีมาเพื่อแก้ปัญหาอะไร
  21. operator overloading คือการทำอะไร
  22. delegate คืออะไร
  23. pointer คืออะไร
  24. วิธีประกาศ pointer ที่ชี้ไปยัง pointer ทำอย่างไร
  25. ก่อนคอมไพล์โค้ดที่มี unsafe ต้องกำหนดอะไร
  26. Implicitly typed local variables มีประโยชน์อย่างไร
  27. lambda expression คืออะไร
  28. object initializes คืออะไร
  29. Anonymous types คืออะไร
  30. Implicitly typed arrays คืออะไร

เฉลยคำถาม

ตอนต่อไป: การเขียนโปรแกรมบนเว็บ

Advertisements

ตอน 31 Implicitly typed local variables และ lambda expression

ไปหน้าแรก | สารบัญ | Laploy.comระเบียนบทความ | บทความจากลาภลอย

เว็บไซต์นี้เป็นตัวอย่างเนื้อหาบางตอนในหนังสือ "เรียนรู้ด้วยตนเอง OOP C# ASP.NET" ครอบคลุม บทที่ 1 ถึงบทที่ 6 (ในหนังสือมี 21 บท) เนื้อหาใน Blog อาจอาจแตกต่างจากในหนังสือเพราะเป็นเนื้อหาที่ยังไม่ได้ตรวจแก้ขัดเกลา (edit)

กดที่นี่เพื่อดูรายละเอียดเนื้อหาในแต่ละบท

กดที่นี่เพื่อไปยังเว็บบอร์ด ถาม-ตอบ 

 

Implicitly typed local variables

 

Implicitly typed local variables (ITLV) คือการใช้ keyword var เพื่อประกาศตัวแปร หรือสมาชิกแบบอื่นๆ โดยไม่ต้องกำหนด data type ซึ่งคล้ายการกำหนด type เป็น object (คือไม่ระบุ data type จริงๆ) หรือคล้ายการประกาศตัวแปรแบบ variant ในภาษา VB6

การใช้ ITLV ดีกว่าการกำหนด type type เป็นแบบ object เพราะเมื่อเรากำหนดให้ตัวแปรมี type เป็น object จะเกิดข้อเสียคือ ก่อนจะนำไปใช้งานได้ เราจะต้องทำ type casting หรือทำ boxing และ unboxing ก่อน ส่วนการใช้ variant อย่างในภาษา VB มีข้อเสียที่ใช้หน่วยความจำมาก และไม่เป็น strongly typed ซึ่งเป็นสิ่งสำคัญใน .NET

ปรกติในภาษา C#2.0 เมื่อเราต้องการประกาศตัวแปร เราจะเขียนแบบนี้

int foo;

หรือ

int foo = 1;

ดังนั้นรูปแบบของการประกาศคือ

<data type> <variable name> = <initializer> ;

จุดสำคัญคือต้องการให้ foo มี data type เป็น integer

การใช้ ITLV ในภาษา C#3.0 ช่วยให้เราสามารถประกาศตัวแปรในลักษณะนี้ได้

var foo = 1;

คำว่า var เป็น keyword ซึ่งทำหน้าที่บอกว่าตัวแปร จะมี data type เป็นเช่นเดียวกับ initializer ยกตัวอย่างเช่น

var foo = 1.2;

foo จะกลายเป็น decimal

var foo = “Good bye”

foo จะกลายเป็น string เป็นต้น

นอกจากนั้นเรายังอาจใช้ ITLV ร่วมกับ object initializes (ดูหัวข้อถัดไปในบทนี้)

ข้อสรุปเรื่อง Implicitly typed local variables
• คือการประกาศตัวแปรโดยไม่ต้องกำหนด type
• Initialize เป็นตัวกำหนด data type

 

Lambda expressions

lambda expression (ต่อไปจะเรียกย่อว่า LE) เป็นคุณลักษณ์ที่เพิ่มขึ้นใน C#3.0 มันทำให้ anonymous method ของ C#2.0 กลายเป็นสิ่งล้าสมัย LE มีประโยชน์ในการเขียนโค้ดที่เกี่ยวข้องกับ delegate, DLINQ และ expression tree LE ช่วยให้การเขียนโค้ดกระชับตามแบบภาษาในตระกูล C และสง่างามตามแบบ C# โปรดพิจารณาตัวอย่างโค้ดต่อไปนี้

x => x > 10

นี่คือ LE ในรูปสามัญที่สุด มันคือนิพจน์บูลลีนที่จะมีค่าเป็นจริงหาก x มีค่ามากกว่า 10 และจะมีค่าเป็นเท็จหากไม่เป็นเช่นนั้น เราสามารถใช้ LE ในภาษา LINQ ได้ หากใช้ภาษา DLINQ (DLINQ คือภาษาใหม่คล้าย LINQ ได้ถูกผนวกเป็นส่วนหนึ่งของ C#3.0 แล้ว) เมื่อคอมไพล์แล้วมักจะพบ LE ปรากฏอยู่ด้วย โปรดพิจารณาตัวอย่างโค้ด DLINQ ต่อไปนี้

var myQ = from myTable in orders, c in customers
where ( myTable.ShipCity == "London")
&& ( myTable.CustomerID == c.CustomerID)
select new {
myTable.OrderDate,
c.CompanyName,
c.ContactTitle,
c.ContactName };

โค้ดข้างบนคือการสืบค้นข้อมูลด้วยภาษา DLINQ ในโค้ดนี้จะเห็นว่ามีตารางสองตาราง อันแรกเป็น object ชื่อ myTable ซึ่งอยู่ภายใน object collection ชื่อ order อีกตารางชื่อ c อยู่ภายในฐานข้อมูลชื่อ customers เราใช้ภาษา DLINQ เพื่อสืบค้นข้อมูลพร้อมๆ กันทั้งสองตาราง โค้ดนี้เมื่อคอมไพล์แล้วจะเป็นดังนี้

var myQ = orders
   .Where(myTable => myTable.ShipCity == "London")
   .SelectMany(myTable => customers
      .Where(c => myTable.CustomerID == c.CustomerID)
      .Select(c => new { myTable.OrderDate,
      c.CompanyName,
      c.ContactTitle,
      c.ContactName }));

ในโค้ดข้างบนมีส่วนที่เป็น LE อยู่สองแห่งคือ

myTable => myTable.ShipCity == "London"

และ

c => myTable.CustomerID == c.CustomerID

ในสองบรรทัดนี้จะเห็นว่า LE อยู่ในรูปแบบ

parameters => expression

โดยบรรทัดแรก parameters คือ myTable ส่วน expression คือ myTable.ShipCity == "London"

C#3.0 ได้นิยาม generic delegate ไว้ให้แล้วจำนวนหนึ่งซึ่งมีรูปแบบดังนี้

delegate T Func<A, T>(A param)
delegate T Func<A0, A1, T>(A0 param0, A1 param1)
delegate T Func<A0, A1, A2, T>(A0 param0, A1 param1, A2 param2)
delegate T Func<A0, A1, A2, A3, T>(A0 param0, A1 param1, A2 param2, A3 param3)

ทำให้เราอาจนิยาม LE ใดๆ โดยไม่ต้องนิยาม delegate ก่อน เพียงใช้งานตามรูปแบบที่นิยามไว้แล้วดังแสดงข้างบน ยกตัวอย่างเช่น LE เพื่อตรวจสอบเลขคู่เป็นดังนี้

Func<int, bool> isEven = i => (i&1) == 0

ข้อสรุปเรื่อง lambda expression
• คือคุณลักษณ์ที่เพิ่มขึ้นใน C#3.0 เพื่อใช้แทน anonymous method ใน C#2.0
• ทำให้นิยาม delegate ได้กระชับขึ้น
• ใช้ร่วมกับภาษา LINQ

ตอนต่อไป Object และ collection initializes

ตอน 30 Operator overloading และ delegate

ไปหน้าแรก | สารบัญ | Laploy.comระเบียนบทความ | บทความจากลาภลอย

เว็บไซต์นี้เป็นตัวอย่างเนื้อหาบางตอนในหนังสือ "เรียนรู้ด้วยตนเอง OOP C# ASP.NET" ครอบคลุม บทที่ 1 ถึงบทที่ 6 (ในหนังสือมี 21 บท) เนื้อหาใน Blog อาจอาจแตกต่างจากในหนังสือเพราะเป็นเนื้อหาที่ยังไม่ได้ตรวจแก้ขัดเกลา (edit)

กดที่นี่เพื่อดูรายละเอียดเนื้อหาในแต่ละบท

กดที่นี่เพื่อไปยังเว็บบอร์ด ถาม-ตอบ 

 

Operator overloading

operator overloading คือการเปลี่ยนหน้าที่ของ operator ให้มีความหมายใหม่ เพื่อใช้งานกับ type ที่เราสร้างขึ้นเอง มีประโยชน์เพราะทำให้เขียนโค้ดใช้งาน type ของเราได้ง่ายขึ้น ได้โค้ดสะอาดเรียบร้อยอ่านง่ายกว่า ภาษา C# สนับสนุนการทำ operator overloading เช่นเดียวกับภาษา C++ ขณะที่ภาษา Java และ VB.NET 1.0 ไม่สนับสนุน

ภาษา C# มี operator หรือตัวกระทำ เช่น + (บวก) – (ลบ) ^ (exclusive or) เพื่อทำหน้าที่กระทำระหว่าง operand (ตัวถูกกระทำ) เช่นในนิพจน์ x + y x และ y คือ operand และ + คือ operator โดย operator เหล่านี้เป็น operator แบบ built-in ที่ถูกนิยามไว้แล้วในตัวภาษา C# เอง ภาษา C# ยินยอมให้เรานิยาม operator ใหม่ขึ้นใช้เองได้ หรือจะนำ operator แบบ built-in มาเปลี่ยนแปลงเพื่อให้ทำหน้าที่อื่นก็ได้ เรียกว่า operator overloading

operator overloading ทำให้เขียนโค้ดได้กระชับขึ้น โปรดพิจารณาตัวอย่างต่อไปนี้


Number foo = new Number();
foo.bar = 10;
foo = foo + 5;

สมมุติว่าเราสร้าง type ใหม่ชื่อ Number (บรรทัดที่ 1) ขึ้น โดยต้องการให้ type นี้เก็บค่า int (บรรทัดที่ 2) และต้องการให้ object ของ type นี้สามารถใช้งานกับ operator + ได้ราวกับมันคือตัวแปร int ตัวหนึ่ง (บรรทัดที่ 3) ก็สามารถทำได้โดยนิยามคลาส Number ดังนี้

1    public class Number
2    {
3       public int bar;
4       public static Number operator +(Number num, int x)
5       {
6          num.bar = num.bar + x;
7          return num;
8       }
9    }

โค้ดข้างบนคือการนิยามคลาสชื่อ Number ซึ่งจะมีการทำ operator overload บรรทัดที่ 3 เรานิยาม field สมาชิกแบบ int เพื่อทำหน้าที่เก็บข้อมูลในคลาสนี้ บรรทัดที่ 4 ถึงบรรทัดที่ 8 คือ method ที่ทำหน้าที่ overload โปรดสังเกตว่า method นี้มีชื่อเดียวกับคลาส (บรรทัดที่ 4) ทำให้มันคล้าย constructor เราจึงอาจจะมองว่ามันเป็น constructor ของการทำ operator overload ก็ได้

โปรดสังเกตต่อไปว่าเราใช้ keyword operator และเครื่องหมาย + เป็นตัวประกาศว่าเราจะทำ overload operator กับเครื่องหมาย + พารามิเตอร์ในวงเล็บคือ operand ของ operator นี้ ในที่นี้เรากำหนดให้ operand ทางซ้ายมี type เป็น Number และ operand ทางขวามี type เป็น int

ภายใน method มีโค้ดสั่งให้นำค่าที่ได้รับมาเป็น argument มาบวกกัน (บรรทัดที่ 6) โดยเป็นการบวกค่าของ field สมาชิก num ของคลาส Number กับค่า integer x และส่งค่ากลับไปเป็น type เดียวกับ operand ทางซ้ายคือ Number (บรรทัดที่ 7)

ข้อสรุปเรื่อง operator overloading
• ใช้เพื่อเปลี่ยนหน้าที่ของ operator
• ใช้ร่วมกับ type ที่เรานิยามขึ้นใหม่
• การนิยามใช้ keyword operator ร่วมกับตัว operator ที่ต้องการ
• ทำได้กับ operator ทุกตัว ยกเว้น = . ?: -> new is sizeof และ typeof

 

Delegate

delegate คือ type ชนิดพิเศษ ความพิเศษของมันคือขณะที่ตัวแปร type อื่นๆ ใช้ทำหน้าที่เก็บข้อมูล (ยกตัวอย่างเช่น int foo ตัวแปร foo ถูกกำหนดให้มี type เป็น int จึงเก็บข้อมูลแบบเลขจำนวนเต็มได้) แต่ตัวแปร delegate ใช้เก็บการอ้างอิงไปยัง method

ผู้สร้าง .NET Framework สร้างกลไก delegate ขึ้นเพราะ delegate มีความจำเป็นในหลายๆ สถานการณ์ เช่น

  • multi-thread: ในภาษา C# เราสามารถวิ่งหลายๆ โปรเซสไปพร้อมๆ กันได้เรียกว่า thread เมื่อจะเริ่ม thread ใหม่เราจำเป็นต้องระบุ method ที่จะให้ไปทำงานโดยอาศัย delegate
  • Library: เมื่อสร้างคลาสไลบรารีบ่อยครั้งที่คลาสจำเป็นต้องเรียกกลับไปยังคลาสผู้ขอใช้บริการ (class client) ซึ่งทำได้โดยอาศัย delegate เท่านั้น
  • Event: โปรแกรมที่ทำงานบนระบบปฏิบัติการ Windows จะพบ event ตลอดเวลา (event-driven programming) เช่นเมื่อผู้ใช้คลิกเมาส์จะมี event Mouse_Click เกิดขึ้น การกำหนดว่าจะให้ไปทำงาน method ใดเมื่อเกิด event จำเป็นต้องอาศัย delegate

delegate ช่วยให้การอ้างถึง method ทางอ้อมสามารถทำได้ ภาษาที่เน้นการใช้งาน pointer อย่างภาษา C++ จะใช้ pointer เพื่อชี้ไปยัง function (ฟังก์ชันในภาษา C++ คือ method ในภาษา C#) เรียกว่า function pointer จึงอาจกล่าวได้ว่า delegate คือ function pointer แบบ OOP และมีคุณสมบัติ type-safety

delegate ทำให้ object สามารถเรียกหา (call back) หรือส่งสัญญาณ (โดยใช้ร่วมกับ event) แจ้งไปยังโค้ดที่ใช้งานมัน เพื่อให้ดำเนินการบางอย่าง เมื่อเกิดเหตุการณ์พิเศษขึ้น ยกตัวอย่างเช่นเรานิยามคลาสชื่อ AlarmClock ทำหน้าที่เป็นนาฬิกาปลุก และนิยามคลาสชื่อ Class1 ซึ่งสร้าง object ชื่อ myAlarm จากคลาส AlarmClock ดังตัวอย่างโค้ดต่อไปนี้

    class AlarmClock
    {
    }
    class Class1
    {
        static void Main(string[] args)
        {
            AlarmClock myAlarm = new AlarmClock();
        }
    }

การทำงานลักษณะนี้คลาส AlarmClock ถือว่าเป็นผู้ให้บริการ (class server) และคลาส Class1 ถือว่าเป็นผู้รับบริการ (class client) ในภาษา C# class client อ้างถึง class server ได้เพราะมีตัวอ้างอิง (ในที่นี้คือตัวแปร แบบ reference ชื่อ myAlarm) เพื่อใช้ติดต่อกับ server ได้ตลอดเวลา แต่ class server ไม่มีตัวอ้างอิงสำหรับใช้เรียกหา class client ได้จึงไม่สามารถ call back ไปที่ class client ได้

ภาษา OOP ที่ใช้ pointer เป็นหลักอย่างภาษา C++ โค้ดส่วน class server สามารถจะใช้ pointer เพื่อเป็นตัวอ้างอิงกลับไปยัง function ที่เรียกใช้งานมันได้ (คือ function pointer) แต่ภาษา C# เมื่อทำงานในแบบ managed code ไม่อาจใช้ pointer ได้ ผู้สร้างภาษา C# จึงประดิษฐ์ delegate ขึ้นเพื่อแก้ปัญหานี้ ต่อไปนี้เป็นโค้ดตัวอย่างแสดงการสร้างและใช้งาน delegate โดยยกตัวอย่างเป็นนาฬิกาปลุกตามที่กล่าวข้างต้น

1    public class AlarmClock
2    {
3       public delegate int ACEventHandler(int i);
4       public event ACEventHandler AlarmNow;
5       private void TimeOut()
6       {
7          AlarmNow(12);
8       }
9    } 

นี้คือโค้ดส่วนที่จะทำหน้าที่เป็น class server ในตัวอย่างนี้ คือนิยามคลาสนาฬิกาปลุกนั่นเอง บรรทัดที่ 3 คือส่วนประกาศ delegate ซึ่งมีส่วนประกอบต่างๆ ห้าส่วนดังนี้

   ส่วนที่    ชื่อหน้าที่ โค้ดในตัวอย่าง

1.     access modifier: public
2.     type declaration: delegate
3.     return type: int
4.     type name: ACEventHandler
5.     parameter: (int i)

รายละเอียดของส่วนประกอบต่างๆ แต่ละส่วนเป็นดังนี้ ส่วนที่ 1 ต้องเป็น public เสมอเพื่อให้มองเห็น (หรือรับรู้) ได้จาก class client ส่วนที่ 2 เป็นการประกาศ type แบบ reference type ที่จะใช้ห่อหุ้ม (encapsulate) method แบบ anonymous

anonymous method เป็น method แบบใหม่ที่เริ่มมีใช้ใน C#2.0 ถูกสร้างมาเพื่ออำนวยความสะดวกในการประกาศ delegate เพราะก่อนหน้า C#2.0 การประกาศ delegate ต้องอ้างถึง method จริงๆ ที่ต้องสร้างเตรียมไว้ต่างหาก ทำให้การเขียนโค้ดยืดเยื้อ แต่ anonymous method ช่วยให้ไม่ต้องสร้าง method จริงๆ ขึ้น เพียงประกาศส่วนหัวของ method ไว้ก็พอ

ส่วนที่ 3 คือ return type ที่จะกำหนดให้เป็น type อะไรก็ได้ตามความต้องการ ส่วนที่ 4 คือชื่อ type ของ delegate นี้ และส่วนสุดท้าย 5 คือพารามิเตอร์ของค่าที่เราต้องการส่งไปยัง class server เมื่อเกิด call back

บรรทัดที่ 4 ประกาศ event ชื่อ AlarmNow เป็น event ที่จะทำหน้าที่ call back ไปยัง class server เมื่อต้องการทำ delegate โปรดสังเกตว่าต้องเรากำหนดให้ AlarmNow มี type ตรงกับ delegate type ที่ประกาศไว้ข้างบน ดังนั้น AlarmNow จะมี return type และพารามิเตอร์ตรงกับ delegate นี้

ต่อมาเรานิยาม method ชื่อ TimeOut() ซึ่งจะถูกเรียกให้ทำงานเมื่อนาฬิกาเดินมาถึงจุดที่ตั้งไว้ (ไม่ได้แสดงในโค้ดตัวอย่าง) ภายใน method นี้มีคำสั่งเพียงบรรทัดเดียวคือสั่งที่ทำหน้าที่ปลุกให้ delegate ทำงานนั่นคือการอ้างถึง event ชื่อ AlarmNow นั่นเอง โปรดสังเกตว่า AlarmNow จะต้องส่งค่าพารามิเตอร์เหมือนที่นิยามไว้ใน delegate โดยในโค้ดนี้พารามิเตอร์มี int เพียงตัวเดียว

เมื่อจัดเตรียมโค้ด delegate ไว้ที class server แล้ว class client ก็ต้องเตรียมโค้ดส่วนรับ delegate ไว้ด้วยซึ่งเป็นเพียงการรับ event ดังนี้

10    class Class1
11    {
12       static void Main(string[] args)
13       {
14          AlarmClock myAlarm = new AlarmClock();
15          myAlarm.AlarmNow +=new ACEventHandler(myAlarm_AlarmNow);
16       }
17       private static int myAlarm_AlarmNow(int i)
18       {
19          return i;
20       }
21    } 

โค้ดข้างบนคือนิยามคลาสชื่อ Class1 ทำหน้าที่เป็น class client บรรทัดที่ 14 สร้าง object ชื่อ myAlarm จากคลาส AlarmClock บรรทัดที่ 15 คือการลงทะเบียนรับ event ชื่อ AlarmNow ที่จะมาจาก myAlarm บรรทัดที่ 17 คือ method ทำหน้าที่ให้บริการเมื่อเกิด event โปรดสังเกตว่า method นี้ต้องมีพารามิเตอร์และ return type ตรงกับ delegate ที่ประกาศไว้ในคลาส AlarmClock

ข้อสรุปเรื่อง delegate

• ช่วยให้ทำ call back ในภาษา C# ได้
• ทดแทนการใช้ function pointer
• ควรใช้ delegate ร่วมกับ anonymous method (C#2.0 ขึ้นไป)เพื่อให้โค้ดกระชับ
• delegate เป็น reference type ชนิดหนึ่ง
• delegate อาจใช้ร่วมกับ event เพื่อทำ call back ก็ได้
• method ตัวที่รับ event ต้องมี signature ตรงกับที่นิยามไว้ใน delegate

ตอนต่อไป: Implicitly typed local variables

ตอน 29 Regex และ Generic

ไปหน้าแรก | สารบัญ | Laploy.comระเบียนบทความ | บทความจากลาภลอย

เว็บไซต์นี้เป็นตัวอย่างเนื้อหาบางตอนในหนังสือ "เรียนรู้ด้วยตนเอง OOP C# ASP.NET" ครอบคลุม บทที่ 1 ถึงบทที่ 6 (ในหนังสือมี 21 บท) เนื้อหาใน Blog อาจอาจแตกต่างจากในหนังสือเพราะเป็นเนื้อหาที่ยังไม่ได้ตรวจแก้ขัดเกลา (edit)

กดที่นี่เพื่อดูรายละเอียดเนื้อหาในแต่ละบท

กดที่นี่เพื่อไปยังเว็บบอร์ด ถาม-ตอบ 

 

Regex (Regular Expression)

 

Regex คือเครื่องมือที่ช่วยในการจัดการกับข้อความ (string) ได้ง่ายและรวดเร็ว การจัดการนี้รวมถึง การค้นหา เปรียบเทียบรูปแบบ แก้ไขเปลี่ยนแปลง กระจายคำ (parse) ตัดทอน ลบ แทนที่ นำมาเชื่อมกัน ซึ่งมีประโยชน์ในการจัดการข้อมูลที่เป็น string เช่นข้อความและไฟล์ HTML เป็นต้น

คลาสต่างๆ ที่จัดการเรื่อง Regex จะอยู่ใน name space System.Text.RegularExpressions;

โค้ดตัวอย่างต่อไปนี้ใช้ method Match.Result เพื่อตัดเฉพาะ protocol และหมายเลข port ออกมาจาก URL ยกตัวอย่างเช่น หากป้อน URL ต่อไปนี้ให้ http://www.contoso.com:8080/letters/readme.html

ผลลัพธ์ที่ได้จะเป็น

http:8080

โค้ดเป็นดังนี้

String Extension(String url)
{
   Regex r = new Regex(@"^(?<proto>\w+)://[^/]+?(?<port>:\d+)?/",       
                        RegexOptions.Compiled);
   return r.Match(url).Result("${proto}${port}");
}

โค้ดตัวอย่างต่อไปนี้ตรวจสอบความถูกต้องของ email address

String Extension(String emailAddress)
{
   Regex r = new Regex("(?<user>[^@]+)@(?<host>.+)");
   return r.Match(emailAddress);
}

รายละเอียดเรื่อง Regex ยังมีพิสดารกว่านี้มาก สามารถนำไปเขียนเป็นตำราได้ต่างหากอีกหนึ่งเล่ม สำหรับขอบเขตของหนังสือเล่มนี้ เท่าที่ผู้เขียนอธิบายมาถือว่าเพียงพอแก่การแล้ว

ข้อสรุปเรื่อง Regex

  • เป็นเหมือนอีกภาษาหนึ่งที่ซ้อนอยู่ภายในภาษา C#
  • มีประโยชน์ในการประมวลผลเกี่ยวกับกลุ่มคำและสายอักขระ
  • คลาสต่างๆ ที่จัดการเรื่อง Regex จะอยู่ใน name space System.Text.RegularExpressions;

 

Generic

Generic เป็นคุณสมบัติใหม่ที่เพิ่มมาในภาษา C#2.0 (และ CLR ด้วย) เป็นกลไกที่ทำให้เราสามารถนิยามคลาสหรือ method ที่อาจกลายเป็น type อะไรก็ได้ในภายหลัง generic เป็นคุณสมบัติที่ทรงพลังที่สุดในบรรดาคุณสมบัติต่างๆ ที่เพิ่มเข้ามาใน C#2.0 เพราะช่วยแก้ปัญหาเรื่องการที่ต้องเขียนโค้ดให้ทำงานกับ type ใด type หนึ่งเป็นการเฉพาะเจาะจง ผู้เขียนขอยกตัวอย่างประโยชน์การใช้งานของ generic ดังต่อไปนี้

ใน C# เวอร์ชันแรกๆ หากเราเขียนคลาสหรือ method เพื่อจัดการกับข้อมูล เราจะใช้ได้กับ type ใด type หนึ่งเท่านั้น ยกตัวอย่างเช่นเขียนคลาสเพื่อจัดการกับ stack ซึ่งจะมี method push() และ pop() ค่าที่เราต้องให้ push() หรือ pop() ต้องเป็น type ใด type หนึ่ง เช่น int หรือ string ดังนั้นหากต้องการ stack ที่จัดการข้อมูลได้หลายๆ แบบเราก็ต้องสร้างคลาส stack หลายๆ แบบซึ่งไม่ใช่วิธีที่ดีในการเขียนโปรแกรม ดังตัวอย่างโค้ดต่อไปนี้

public class IntStack
{
   public void Push(int item) { }
   public int Pop() { }
}
static void Main()
{
   IntStack stack = new IntStack();
   stack.Push(1);
   int number = stack.Pop();
}

โค้ดข้างบนคือตัวอย่างคลาส stack ที่จัดการกับข้อมูลแบบ int และคลาสที่เรียกใช้ stack ดูตัวอย่างต่อไป

public class StringStack
{
   public void Push(string item) { }
   public string Pop() { }
}
static void Main()
{
   StringStack stack = new StringStack();
   stack.Push("1");
   string number = stack.Pop();
} 

โค้ดข้างบนคือตัวอย่างคลาส stack ที่จัดการกับข้อมูลแบบ string และคลาสที่เรียกใช้ stack

วิธีแก้ไขอย่างเดียวที่ทำได้คือสร้าง stack ที่รับส่งข้อมูลเป็น object type ซึ่งสามารถแปลงกลับเป็น type อะไรก็ได้ แต่วิธีนี้มีข้อเสียคือโค้ดที่เรียกใช้ต้องทำ boxing ก่อน และคลาส stack ต้องนำ object มา unboxing อีกครั้งก่อนจะนำไปเก็บใน stack ได้ การทำ boxing และ unboxing ตลอดเวลาทำให้โปรแกรมด้อยประสิทธิภาพ นอกจากนั้นการทำ boxing และ unboxing ยังทำให้สูญเสียความเป็น type safety อีกด้วย

public class Stack
{
   public void Push(object item) {}
   public object Pop(){}
}
static void Main()
{
   Stack stack = new Stack();
   stack.Push("1");
   stack.Push(1);
   string number = stack.Pop().ToString();
   int number2 = (int)stack.Pop();
}

โค้ดข้างบนคือตัวอย่างคลาส stack ที่จัดการกับข้อมูลแบบ object และคลาสที่เรียกใช้ stack โปรดสังเกตว่าการใช้งานต้องทำ boxing และ unboxing ตลอด

generic ถูกสร้างมาเพื่อแก้ปัญหานี้ มันทำให้เราสามารถนิยามคลาสหรือ method ที่ใช้กับ type อะไรก็ได้โดยไม่ทำให้โปรแกรมด้อยประสิทธิภาพและสูญเสียความเป็น type safety โปรดพิจารณาโค้ดตัวอย่างต่อไปนี้

1    public class Stack<T>
2    {
3       public void Push(T item) { }
4       public T Pop() { }
5    }
6    static void Main()
7    {
8       Stack<int> stack = new Stack<int>();
9       stack.Push(1);
10      stack.Push(2);
11      int number = stack.Pop();
12   } 

โค้ดข้างบนคือตัวอย่างคลาส stack ที่จัดการกับข้อมูลแบบ generic และคลาสที่เรียกใช้ stack โปรดสังเกตว่าชื่อคลาสจะตามด้วย <T> (บรรทัดที่ 1) ซึ่งเป็นการประกาศว่าคลาสนี้เป็นแบบ generic โปรดสังเกตต่อไปอีกว่า argument ของ method Push()(บรรทัดที่ 3) และ return type ของ method Pop()(บรรทัดที่ 4) มี type เป็น T ซึ่งหมายถึง generic ด้วยเช่นกัน

โปรแกรมที่เรียกใช้คลาส stack (คือ Main) จะเป็นผู้กำหนดว่าคลาส stack จะมี type เป็นแบบใด ในโค้ดตัวอย่างกำหนดให้เป็น int (บรรทัดที่ 8) จากนั้นจะสามารถ push ข้อมูลเป็น int ได้ (บรรทัดที่ 9 และ 10) และ pop ข้อมูลมาเป็น int (บรรทัดที่ 11) ได้เช่นเดียวกัน

นอกจากใช้ generic กับคลาสแล้ว เรายังสามารถใช้ generic กับสมาชิกของคลาสได้ด้วย เช่น

ใช้ generic กับสมาชิกของคลาสที่เป็น field

class test<T>
{
    private T foo;
} 

ใช้ generic กับสมาชิกของคลาสที่เป็น property

public T Foo
{
   get { return foo; }
   set { foo = value; }
} 

ใช้ generic กับสมาชิกของคลาสที่เป็น method (ใช้ได้ทั้ง return type และพารามิเตอร์)

public T Foo(T bar)
{
   return bar;
} 

ข้อสรุปเรื่อง generic

  • generic เริ่มมีใช้ใน C#2.0
  • generic อยู่ใน name space System.Collections.Generic ของ .NET Framework class library เวอร์ชัน 2.0 ขึ้นไป
  • generic ช่วยแก้ปัญหาการที่โค้ดต้องทำงานกับ type ใด type หนึ่งได้เท่านั้น
  • generic ปลดเปลื้องภาระของ heap จาก boxing และ unboxing
  • จะใช้ generic กับคลาสหรือสมาชิกของคลาสก็ได้

ตอนต่อไป: Operator overloading