C# 3.0: ล้ำอีกระดับ (III - Final)

Posted 18/01/2008 01:22
by nikom
คะแนนนิยม

สายตาของพวกเรายังคงต้องเกาะกระแสเทคโนโลยี Visual Studio 2008 และ C# 3.0 ต่อไป ถึงแม้บทความชุดนี้จะเผยแพร่ออกมาครบแล้ว ในแวดวงนักพัฒนาซอฟท์แวร์อย่างพวกเรานั้นภาษาโปรแกรมและเครื่องมือมีระดับคืออันดับต้นๆของความต้องการ นอกจากพวกมันจะอำนวยให้ทุกอิริยาบถของนักพัฒนาสะดวกและสนุก ประสิทธิภาพในทุกเม็ดหน่วยของแอพพลิเคชันยังขึ้นอยู่กับพวกมันด้วย เมื่อใดก็ตามที่เชี่ยวและลึกซึ้งกับภาษาโปรแกรมและเครื่องมือมีระดับได้ที่แล้ว พวกเราก็สามารถเป็นกำลังหลักให้วงการพัฒนาซอฟท์แวร์บ้านเราได้

Local Type Inference, Object Initialization, Anonymous Type, และ Lambda Expression 4 อาวุธใหม่ของ C# 3.0 ถูกนำเสนอกันไปแล้วโดยบทความชุดนี้ ถ้าหูพร่าตาลาย รออ่านอีก 2 อาวุธที่กำลังเข้าคิวมาบดสมองกันก่อน แล้วค่อยไปหายากินทีเดียวไปเลย

มาถึงอาวุธที่ 5 ซึ่งถ้าจำกันได้มันถูกเผยตัวไปนิดนึงในตอนที่แล้วช่วงท้าย ใช่ครับ..กำลังหมายถึง Extension Methods ลองไปดูซอร์สโคดที่เตรียมไว้ให้ในตอนที่แล้วสำหรับทดลองกันอีกที นิยามของคลาส PredicateCollector ขอยกมาแสดงตรงนี้

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static class PredicateCollector
{
    public static Expression<Func<T, bool>> True<T>() { return f => true; }
    public static Expression<Func<T, bool>> False<T>() { return f => false; }
 
    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expA,
                                                        Expression<Func<T, bool>> expB)
    {
        var targetExp = Expression.Invoke(expB, expA.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>
              (Expression.Or(expA.Body, targetExp), expA.Parameters);
    }
 
    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expA,
                                                         Expression<Func<T, bool>> expB)
    {
        var targetExp = Expression.Invoke(expB, expA.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>
              (Expression.And(expA.Body, targetExp), expA.Parameters);
    }
}

เมธอด Or<T> และ And<T> คือ Extension Methods ครับ สังเกตอย่างไรน่ะเหรอ ก็คีย์เวิร์ด 'this' ตรงพารามิเตอร์แรกของเมธอดทั้งสองนั่นเอง ภาษาบ้านๆพูดได้ว่าไทป์ที่ 'this' มาคุมอยู่จะเป็นเจ้านายของพวกมัน เรียกใช้ Extension Method นั้นๆได้ ไทป์อื่นแหยมไม่ได้ ยกเว้นสแตติกคลาสที่นิยามพวกมัน

ตัวอย่างข้างต้นนั้น Expression<Func<T, bool>> ( ที่โดยพื้นเพก็ Expression<T> นั่นแหละ ) คือไทป์ที่เป็นเจ้าของเมธอด Or<T> และเมธอด And<T> ซึ่งหมายถึงมีสิทธิเรียกใช้เมธอดทั้ง 2 ได้ ว่าแล้วก็ไปดูตอนเรียกใช้กันดีกว่า

1
2
3
4
5
6
7
8
9
10
11
12
13
private static IQueryable<Product> ProductsByConditions(params string[] conditions)
{
    string country = conditions[0];
    double price = double.Parse(conditions[1]);
    string category = conditions[2];
 
    var predicate = PredicateCollector.True<Product>();
    predicate = predicate.And(p => p.CountryForSale == country);
    predicate = predicate.And(p => p.Price < price);
    predicate = predicate.And(p => p.Category == category);
 
    return _Products.Where(predicate);
}

บรรทัดที่ 8 ถึง 10 คือตัวอย่างการเรียกใช้ 'And<T>' โดยผู้เรียกคือ 'predicate' ซึ่งเป็นอินสแตนส์ (อ็อบเจกท์นั่นแหละ) ของไทป์ Expression<Func<T, bool>> ที่ถูกสร้างขึ้นในบรรทัดที่ 7 ซึงความสามารถ Local Type Inference เข้ามามีบทบาทอยู่ด้วย (สังเกต 'var')

เริ่มพึมพำกันหรือยังว่า 'นี่เองรูปลักษณ์ที่ไมโครซอฟท์หมายมั่นให้ใช้งานกันสำหรับ Extension Methods'  เมธอด And<T> ถูกเรียกใช้ในบรรทัดที่ 8 ถึง 10 ราวกับเป็นเมมเบอร์ของ Expression<T> ลองเข้าไปดูนิยามของ Expression<T> ดูก็ได้ จะเห็นว่าไม่มีเมธอด And<T> อยู่ข้างใน

แต่ในทั้ง 3 บรรทัดนั้น หากพิมพ์คำ predicate แล้วต่อด้วย '.' อินเทลลิเซนส์ของ Visual Studio 2008 beta จะแสดงเมธอด Or<T> และ And<T> อยู่ในดร็อปดาวน์ลิสท์ด้วย

VS2008beta Intellisense

กล่าวได้อีกอย่างว่า ด้วย Extension Methods จะทำให้ไทป์ของพารามิเตอร์แรก (ที่มี 'this' กำกับอยู่) เสมือนได้เมมเบอร์เมธอดเพิ่มขึ้นมา ภาษาบ้านๆพอเรียกได้ว่าได้ลูกน้องเพิ่มมาให้คอยเรียกใช้

รูปด้านบนคือโคดที่กำลังจะใช้ Extension Method โคดบรรทัดนี้ถ้าอยู่ในเนมสเปสเดียวกันกับสแตติกคลาสที่บรรจุ Extension Method นั้นไว้ก็ไม่มีปัญหาอะไร แต่ถ้าอยู่ต่างไฟล์หรือต่างเนมสเปสกัน ต้องไม่ลืมผนวกเนมสเปส (ด้วยสเตทเมนท์ using <Namespace_Name>;) โดย Namespace_Name คือชื่อเนมสเปสที่ Extension Methods ถูกสร้างไว้ และนี่เป็นหนึ่งในเอกลักษณ์ที่สำคัญครับ

ตามซอร์สโคดที่เตรียมไว้ในตอนที่แล้วจะเห็นว่าสแตติกคลาส 'PredicateCollector' อยู่ในเนมสเปส LinqDemo2 และนั่นหมายถึงอินสแตนส์ของ Expression<Func<T, bool>> ที่อยู่ในไฟล์เดียวกัน จะมองเห็นและเรียกใช้เมธอด Or<T> และ And<T> ได้ไม่มีปัญหา แต่โคดในเนมสเปสอื่นต้องใส่สเตทเมนท์ using LinqDemo2; ไว้จึงจะเรียกใช้ได้

มีอีกตัวอย่างของ Extension Method ให้ดู ซอร์สโคดก็มีให้ดาวน์โหลดกันอีกเหมือนเดิม ซึ่งจะเห็นว่ามันก็ใกล้เคียงกับตัวอย่างของบทความตอนที่แล้ว แต่ครั้งนี้จะแยกคลาส Product ไปไว้อีกไฟล์ (Production.cs) และในไฟล์เดียวกันนี้จะใส่สแตติกคลาสชื่อ Extensions เข้าไปด้วย

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static class Extensions
{
    public static string DisplayAsString(this Product product)
    {
        StringBuilder result = new StringBuilder(string.Empty);
 
        result.AppendFormat("ProductID: {0}{1}", 
                                product.ProductID.ToString(), 
                                Environment.NewLine);
        result.AppendFormat("CountryForSale: {0}{1}",
                                product.CountryForSale.ToString(),
                                Environment.NewLine);
        result.AppendFormat("Price: {0}{1}",
                                product.Price.ToString(),
                                Environment.NewLine);
        result.AppendFormat("Category: {0}{1}",
                                product.Category.ToString(),
                                Environment.NewLine);
        return result.ToString();
    }
}

Extension Method ในคลาส Extensions ก็อย่างที่เห็นกัน DisplayAsString ทำหน้าที่แสดงรายละเอียดของพร็อพเพอตีต่างๆในคลาส Product ออกมาเป็นสตริง โคดที่เรียกใช้นั้นอยู่ต่างไฟล์ จึงต้องเพิ่มสเตทเมนท์ using LinqDemo02.Production; ก่อนจะเรียกใช้ได้ ข้างล่างนี่คือหน้าตาโคดที่เรียก Extension Method ดังกล่าว

1
Console.WriteLine(prod.DisplayAsString());

เคยเห็นเมธอด ToString กันใช่มั้ยครับ บางคนอาจคิดว่าตั้งชื่อตามลักษณะนิยมแบบนี้จะดีกว่าชื่อ DisplayAsString หรือเปล่า อาจจะดูดีกว่าก็จริง แต่จะไปติดตรงเอกลักษณ์สำคัญอีกอย่าง ที่ว่าถ้ามีเมมเบอร์เมธอดที่มีชื่อเดียวกันอยู่แล้วในนิยามของคลาส จะทำให้ Extension Method ไม่มีสิทธิ์เผยอหน้า เมมเบอร์เมธอดจะถูกเลือกให้ทำงาน ฟังดูต่ำต้อย แต่นี่คือลิขิตฟ้า (specification ของ C# 3.0)

สรุปเอกลักษณ์ที่สำคัญของอาวุธอย่าง Extension Methods กันหน่อยละกัน

  • Extension Methods ต้องถูกนิยามใน 'สแตติกคลาส' เท่านั้น
  • ไทป์ที่สืบทอด (inherit) จากไทป์ที่เป็นเจ้านายของ Extension Methods จะได้รับ Extension Methods ไปด้วย แต่เฉพาะที่เป็น public เท่านั้น
  • คีย์เวิร์ด 'this' ต้องนำมาใช้กำกับอยู่หน้าพารามิเตอร์ตัวแรกของ Extension Methods เสมอ เสมือนเป็นการระบุว่าไทป์ไหนเป็นเจ้านายมัน
  • using <Namespace_Name>; เป็นสเตทเมนท์จำเป็น ถ้าต้องการเรียกใช้ Extension Methods ที่ถูกสร้างอยู่ต่างไฟล์หรือต่างเนมสเปสกัน โดย Namespace_Name คือชื่อของเนมสเปสที่ 'สแตติกคลาสที่บรรจุ Extension Methods' ถูกสร้างไว้
  • กรณีมีเมมเบอร์เมธอดของไทป์ที่ชื่อเหมือนกันกับ Extension Method อยู่ เมมเบอร์เมธอดนั้นจะถูกเรียกมาทำงาน ส่วน Extension Method น่ะต้องรอให้คนเขียนโคดมาเปลี่ยนชื่อให้แก้เคล็ด

แล้วก็มาถึงอาวุธสุดท้ายอาวุธที่ 6 ที่ถูกเรียกขานกันว่า Query Expressions ไปดูตัวอย่างหน้าตาของมันกันก่อนเลยดีกว่า ซอร์สโคดสมบูรณ์ที่ทำงานได้กับ Visual Studio 2008 beta ก็มีให้ดาวน์โหลดอีกเช่นเดิม

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static ProductList<Product> _Products;
 
static void Main(string[] args)
{
    _Products = new ProductList<Product> 
                    { new Product { ProductID = 1, CountryForSale = "Thailand", Price = 39.5 , Category="Men" }
                    , new Product { ProductID = 2, CountryForSale = "Thailand", Price = 49.5 , Category="Women" }
                    , new Product { ProductID = 3, CountryForSale = "Thailand", Price = 59.5 , Category="Men" }
                    , new Product { ProductID = 4, CountryForSale = "Malaysia", Price = 39.5 , Category="Women" }
                    , new Product { ProductID = 5, CountryForSale = "Malaysia", Price = 49.5 , Category="Men" }};
 
 
    var query = from prod in _Products
                where prod.Category == "Women"
                select new { prod.ProductID, prod.Category };
 
    foreach (var x in query)
    {
        Console.WriteLine("ProductID: {0}, Category: {1}", x.ProductID.ToString(), x.Category);
        Console.ReadLine();
    }
}

บรรทัดที่ 12 ถึง 14 ที่เห็นเหมือนๆกับสเตทเมนท์ SQL นั่นแหละคือ Query Expression โดยที่มันต้องเริ่มด้วยท่อนของ 'from' ก่อน เพราะมันเป็นเสมือนการวนลูปทำงานกับทุกออบเจ็กท์ในคอลเลคชัน (จากตัวอย่างก็คือ _Products)

แต่ละออบเจ็กท์นั้น (prod ซึ่งมีไทป์เป็น Product) จะนำไปตรวจสอบกับเงื่อนไข (Predicate) อย่างที่เห็นใน 'where' สะดุดกับคำว่า Predicate มั้ยครับ บางคนคงเดาถูกว่ากำลังหมายถึง Lambda Expression นั่นเองที่ใช้อยู่ในท่อน 'where'

สุดท้ายจะรีเทิร์นด้วย 'select' ที่เป็นการหล่อและปลุกเสกคอลเลคชัน เห็นท่อน 'select' ข้างบนต้องจำกันได้แล้วนะครับ (หวังจากคนอ่าน) ว่าสิ่งที่บรรจุในคอลเลคชันคือ .....ครับ มันคืออินสแตนส์ของ Anonymous Type จุดสังเกตคือ 'new' ที่ต่อด้วยบล็อค { } ทันที บรรยายไปแล้วในตอนแรกของบทความชุดนี้นั่นเอง

ผลลัพธ์จากซอร์สโคดควรจะเป็น

ProductID: 2, Category: Women

ProductID: 4, Category: Women

ไปไล่ดูในเมธอด CreateQuery ของคลาส ProductList ในซอร์สโคดที่มีให้ดาวน์โหลดกันนะครับ ว่าท่อน 'where' และท่อน 'select' นั้น Lambda Expression เข้ามามีส่วนร่วมอย่างไร

บทความชุดนี้มาถึงจุดสุดท้ายแล้วครับ อย่างที่กล่าวไว้ในย่อหน้าแรกของบทความตอนนี้ว่านักพัฒนาอย่างพวกเรายังคงต้องจับตากับกระแสเทคโนโลยี Visual Studio 2008 และ C# 3.0 ต่อไป บทความชุดนี้จะเป็นพื้นแน่นๆให้ผู้สนใจได้ ก่อนจะไปลุยทำงานกันจริงๆ

 

 

***บทความทั้งหมดในชุด***

C# 3.0: ล้ำอีกระดับ (I)

C# 3.0: ล้ำอีกระดับ (II)

C# 3.0: ล้ำอีกระดับ (III)

Published Jan 18 2008, 01:22 AM by nikom
Filed under: ,
No Comments
(required)  
(optional)
(required)  
Add
คอแหลม
โฆษณาออนไลน์,
				โฆษณา,ออนไลน์,ลงโฆษณา,ประกาศ,online advertising,online
				,advertising,โปรโมทสินค้า,โปรโมทเว็บไซต์,promote website,
				seo,pay per click,ad per click,media,ค้นหาเว็บ,media,
				สื่อ