Silverlight 完全中文解決方案

    技术2022-05-11  8

    LINQ 首部曲 : LINQ To Object Part 1     文/黃忠成     序曲: LINQ 的架構與程式語言     Microsoft 於新一代的.NET Framework 3.5中增加了幾個新功能,其中之一就是LINQ,與其它新功能不同,架構上,LINQ是一個Framework方式呈現,理論上可以使用於任何的.NET Language中,但她的真正威力必須要程式語言配合才能夠完全的發揮,圖1為LINQ的架構概觀圖。 [ 圖1] 如圖1所示,LINQ Framework大致分為三大部份,各自因應不同的資料來源,LINQ To Object Framework用來對物件查詢,LINQ To XML Framework用於查詢XML物件,LINQ To ADO.NET Framework 又可細分為三個子集:LINQ To DataSet Framework用來對DataTable、DataRow等物件做查詢,LINQ To SQL Framework則用於對資料庫的查詢,LINQ To Entity Framework則是與 ADO.NET Entity Framework整合。在LINQ Framwork之上的,是程式語言編譯器所提供的LINQ Expression語法支援,如同前面所提及的,LINQ Framework本身是一組與程式語言無關的Framework,藉助於編譯器所提供的LINQ Expression支援,讓設計師能更輕鬆的撰寫LINQ應用程式。舉例來說,在C#中可以用 的LINQ Expression語法來取代對LINQ To Object Framework的函式呼叫 ,此處的Where函式是LINQ To Object Framework所提供的,下文會對此有更詳細的介紹。基本上,語言編譯器有義務對於如LINQ To Object、LINQ To XML、LINQ To ADO.NET提供一致性的LINQ Expression語法規則,這可以讓設計師只學習一種語法,就能應用於不同的語言中。LINQ的出現,代表著程式語言將走向下一個階段,正如其全名『Language Integrated Query』所表現的意義,程式語言將與查詢語言整合,為設計師提供更快速、方便的查詢功能,更甚之!LINQ中的LINQ To SQL功能正試圖整合各資料庫廠商所各自為政的SQL語言,其架構中的LINQ Provider機制,允許設計師為不同的資料庫撰寫Provider,將LINQ的語法轉換成該資料庫所能接受的語法,如圖2所示: [ 圖2]   從一個簡單的LINQ程式開始     LINQ 架構中分成了三大部份,LINQ To Object、LINQ TO ADO.NET、LINQ TO XML,因此本系列文章也分成了三個階段,在此階段中,筆者將以LINQ To Object Framework為主軸,為讀者們介紹其基本用法,與其它的文章不同,本文同時會嘗試討論LINQ To Object Framework的幕後機制,將LINQ To Object Framework身上所被的簡潔外衣去除,讓讀者們一窺其設計之巧妙之處,首先從一個簡單的LINQ To Object Framework程式開始。 [ 程式1] private static void TestSimpleLinq() {       string[] list = new string[] { "1111", "2222", "3333" };       var p = from o in list select o;       foreach (var s in p)            Console.WriteLine(s);  } 程式碼中,斜體字部份就是C#所提供的LINQ Expression語法,意思是從list這個字串陣列中,取出一個列舉物件(IEnumerable),放到p變數中,讀者們應該已發覺到,p變數是以var方式宣告的,var是C# 3.0的新關鍵字,意指其型態是由右方運算式所指定,本文後面會詳述其用法及限制,在此處,請將她視為是由編譯器依據右方運算式的傳回值所決議的型別。此程式執行後的結果如圖3。 [ 圖3] 當然,如果只是要列出list陣列中的所有元素,只要以foreach指令來一一擷取即可,何需大費週章寫下from….的指令!是的!但LINQ To Object Framework的能力自然不止於此,請看程式2。 [ 程式2] private static void TestConditionLinq() {       string[] list = new string[] { "1111", "2222", "3333" };       var p = from o in list where o == "2222" select o;       foreach (var s in p)          Console.WriteLine(s);  } 與程式1不同,程式2中的LINQ Expression中包含了where語句,這意味著LINQ允許設計師以類SQL語法對陣列做查詢,更確切的說是,LINQ允許設計師以類SQL語法對實作了IEnumerable或IQueryable介面的物件做查詢(於LINQ TO SQL時會談到IQueryable介面)。如果你和筆者一樣,常常與SQL為伍,相信你很快會寫下如程式3的程式碼,來測試LINQ Expression的where語句。 [ 程式3] var p = from o in list where o like "1%" select o; 很不幸的,like條件式並不存在於LINQ Expression的語法規則中,相對的,LINQ To Object Framework允許設計師以函式呼叫的方式來達到類似的結果。 [ 程式4] var p = from o in list where o.Contains("2") select o; 這段程式結合了string物件的Contains函式來做查詢,這意味著LINQ To Object Framework不僅是程式語言所提供的查詢語法,其與程式語言整合的程度更是異常緊密。雖然LINQ Expression還有許多如Grouping、Orderby、Join等能力,但目前筆者不想耗費太多時間在其語法規則上,將其留待後文再討論,目前先將焦點放在LINQ To Object Framework是如何達到這些效果的課題上。   這是如何辦到的?     C# 3.0 及.NET Framework 3.5在目前是維持在以.NET Framework 2.0為基礎所開發的子集,這代表著C# 3.0所提供的LINQ Expression不會一成不變的出現在MSIL 2.0中,C# 3.0一定會把程式轉換成MSIL 2.0所規範的IL Code,這裡沒有from xxxx in yyy的LINQ Expression,所以如果想知道LINQ To Object Framework如何完成這神奇任務的,第一步就是要知道C# 3.0把我們的程式變成什麼樣子,這有許多工具可以達到,首選的工具自然是陪伴.NET設計師多年的Relfector。 [ 程式5] private static void TestConditionLinq() {     IEnumerable p = new string[] { "1111", "2222", "3333" }.Where (delegate (string o) {         return o == "2222";     });     foreach (string s in p)    {         Console.WriteLine(s);     }     Console.ReadLine(); } 咦!何時string陣列有名為Where的成員函式了?不是的,這是C# 3.0的新特色之一:Extension Method,當於Reflector所反組譯的視窗中點選了Where函式後,Reflector會帶我們到System.Linq.Enumerable類別中定義的Where靜態成員函式中。看來了解LINQ To Object Framework前,得先弄清楚C# 3.0所提供的幾個新功能了。   了解LINQ前的準備: C# 3.0 New Feature     C# 3.0 提供了許多新功能,其中與LINQ緊密相關的有四個:Implicit Local Variable、Extension Method、Lamba Expression、Anonymous Type。   C# 3.0 Implicit Local Variable     Implicit Local Variable 就是先前所使用的var型態宣告,她允許設計師指定某個變數為var型態,其真正型態將由編譯器從右方運算式推算而來,程式7演示了Implicit Local Variable的用法。 [ 程式7] static void TestImplicitLocalVariable() {        var vint = 10;        var vstring = "TEST";       var vint64 = 9029349442;        var vdouble = 9.234;        Console.WriteLine("{0},{1},{2},{3}", vint.GetType().ToString(),                                        vstring.GetType().ToString(),                                        vint64.GetType().ToString(),                                        vdouble.GetType().ToString());        Console.ReadLine();  } var 是由右方運算式所賦與型別,所以右方運算式也可以是一個函式,規則上var僅能用於Local Variable(區域變數)的宣告,無法使用於class variable、function parameter等其它地方,也就是說程式8的用法皆不符合規則。 [ 程式8] class Program {         private static var t = 15;         static void TestImplicitLocalVariable(var t) {} }   C# 3.0 Extension Method     Extension Method 允許設計師宣告一個靜態函式,此函式必須存在於一個靜態類別中,在C# 3.0中,她將會被視為指定型別的靜態成員函式(這只是看起來像是,事實上她仍然是其所在類別的靜態成員函式),前例中LINQ To Object Framework的Where函式其實是位於System.Linq.Enumerable這個靜態類別中。在C# 3.0中可以直接用string[].Where的函式呼叫語法來呼叫此函式,編譯器會將此展開成對System.Linq.Enumerable.Where(IEnumerable…)函式的呼叫(string陣列是實作了IEnumerable介面的物件,所以可以傳入Where函式中)。為了讓讀者們更了解Extension Method,筆者寫了個小程式來演示Extension Method的用法。 [ 程式9] using System; using System.Collections.Generic; using System.Linq; using System.Text;   namespace MyExtensionMethod {     public static class TestExtensionMethod     {         //extnsion methods must be defined in non generic static class.         public static int WordCount(this string v)        {             return v.Length;         } } } using System; using System.Collections.Generic; using System.Linq; using System.Text; using MyExtensionMethod;   namespace CSharpNew {     public static class TestExtensionConsumer   {         public static void TestExtension()   {             string s = "TEST";             Console.WriteLine(s.WordCount());             Console.ReadLine();         }     } } Extension Method 必須宣告在一個非泛型的靜態類別中,而且必須要是靜態函式,其參數的第一個就是欲Extension的型別(Type),並且要以this語句做為識別字。使用時,當Extension Method所在的namespace與使用端的namespace不同時,需以using來引入其namespace。   Extension Method Generics assumption     Extension Method 遇上 generics 時, 情況會顯得很有趣,請看程式 10 的例子。 [ 程式 10] using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; namespace FirstLinq {     public class ExtensionMethodAndGenerics    {         private static void GenericTypeResovlerTest1()       {             GenericTypeResolverTest v = new GenericTypeResolverTest();             v.Value = "TEST2";             v.Test();         }     }     //generic implicit type resolver.     public class GenericTypeResolverTest    {         public string Value { get; set; }         public override string ToString() {             return Value.ToString();         }     }     public static class GenericTypeResolverMethodTest   {         public static void Test (this T obj)       {             Console.WriteLine(obj.ToString());         }     } }   請注意程式中 Test 這個 Extension Method 的定義,她是一個 generic method ,一般來說,在呼叫 generic method 時,我們必需指定 type parameter ,譬如程式 11 片段。 [ 程式 11] Test () 但此處卻在未提供 type parameter 的情況下呼叫此 Extension Method ,而 C# 編譯器也接受了這種寫法,這是為何呢?答案就是 Extension Method 會進行一種 type parameter assumptio n 的動作,也就是由呼叫端假設被呼叫端的 type parameter,本例中,呼叫Test函式時是透過GenericTypeResolverTest型別的物件,因此C# 編譯器便假設呼叫Test函式時的type parameter為GenericTypeResolverTest型別。基本上,這樣的type parameter assumption可以簡化呼叫Extension Method的動作,也不難理解。但LINQ To Object Framework所應用的技巧就不太好理解了,請看另一個例子:程式12。 [ 程式12] using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text;   namespace FirstLinq {     public class ExtensionMethodAndGenerics    {         private static void GenericTypeResovlerTest1()       {                       GenericTypeResolverTest[] v2 = new GenericTypeResolverTest[]{                                                new GenericTypeResolverTest(),                                                new GenericTypeResolverTest()};             v2.Test2();         }     }     //generic implicit type resolver.     public class GenericTypeResolverTest    {         public string Value { get; set; }         public override string ToString()        {             return Value.ToString();         }     }     public static class GenericTypeResolverMethodTest   {               public static void Test2 (this IEnumerable obj)     {             Console.WriteLine(obj.ToString());         }     } } 這個例子中,呼叫 Test2 函式時是透過一個 GenericResolverTest 陣列,依據 generic type assumption 的規則,我們很直覺的設想 T 應該是被推算為 GenericResolverTest 陣列,但事實並非如此,請注意 Extension Method 的宣告,其針對的是 IEnumerable 型態,因此此時的 type parameter 會變成 IEnumerable ,而 C# 中的陣列實作了 IEnumerable 介面,以本例來說,呼叫 Test2 函式時,呼叫端的型別被視為是 IEnumerable ,也就是說 Extension Method 中的 T 將被替換為 GenericResolverTes t ,最後結果如程式13。 [ 程式13] void Test2                (IEnumerable                 obj) void Test2 (IEnumerable obj)   C# 3.0 Lamba Expression     Lamba Expression 並未出現在Reflector所反組譯的程式碼中,事實上!她是隱含性的存在,Lamba Expression用來簡化C#中指定anonymous delegate的程式碼,程式14的anonymous delegate轉成Lamba Expression後就成為了程式15所示。 [ 程式14] IEnumerable p = new string[] { "1111", "2222", "3333" }.Where (delegate (string o) {         return o == "2222";     }); [ 程式15] var p = new string[] { "1111", "2222", "3333" }.Where ( l => l == "2222"); 很明顯的,Lamba Expression確實簡化了程式碼(少打了許多字不是?),不過老實說,筆者初次看到Lamba Expression時,的確對其語法很不習慣,直到筆者寫下了程式16的Lamba Expression對於Lamba Expression的不適感才稍減許多。 [ 程式16] ………… namespace CSharpNew {     public class TestLamba    {         public delegate int Sum(int x, int y);         public void Test2()        {             //lamba expression can be more complex.             Sum sFunc = (x, y) => {                             var ret = x + y;                             DateTime d = DateTime.Now;                             Console.WriteLine("sum time is {0}",d.ToShortDateString());                             return ret;             };             Console.WriteLine(sFunc(15, 20));             Console.ReadLine();         }     } } 如你所見,Lamba Expression是簡化了anonymous delegate的宣告,以較簡潔的語法完成,針對單行式的程式碼,Lamba Expression就連{}及return部份都簡化掉了。   Anonymous Type     另一個C# 3.0與LINQ相關的特色就是Anonymous Type,也就是匿名型別,簡略的說,C# 3.0允許設計師以一個簡潔的語法來建立一個類別,如程式17。 [ 程式17] var p1 = new[]{new {Name = "code6421", Address = "Taipen", Title = "Manager"},                           new {Name = "tom", Address = "Taipen", Title = "Manager"},                           new {Name = "jeffray", Address = "NY", Title = "Programmer"}}; 此例中,編譯器將會自動為我們建立一個擁有Name、Address、Title三個public field的類別,並按照語法賦與其值,請注意 !此例中僅會建立一個匿名類別,而非三個!這意味著編譯器在處理匿名類別時,會先行判斷目前所要建立的類別是否已經存在了,若已存在則直接取用,而比對的方式就是語法中所指定的public field數目及名稱,這是效率及程式大小的考量。規格上Anonymous Type中僅允許宣告public field,其它如method、static field等都不允許出現。 PS: ( 在筆者探索LINQ Framework時,曾發生一個小插曲,讓筆者不得不懷疑在C# 3.0的內部版本中,曾經出現允許宣告成員函式的Anonymous Type設計。)   再訪LINQ To Object Framework     OK ,現在可以確定一件事,前面所看到的System.Linq.Enumerable類別就是LINQ To Object Framework的一部份,LINQ To Object Framework是以泛型為本、Extension Method為輔、並用Lamba Expression簡化後的產物,再加上程式語言如C# 3.0、VB.NET 3.0的幫助,才變成了現在所看到的簡潔語法。   以Extension Method為起點     在此節中,我們先將腳步停留在編譯器與LINQ To Object Framework的結合階段,筆者有個小程式要展現給讀者們。 [ 程式18] using System; using System.Collections.Generic; using System.Linq; using System.Text;   namespace FirstLinqHack {     class TestHacking    {         public static void HackLinq()        {             Persons list = new Persons ();             list.Add(new Person { Name = "Code6421", Age = 18, Address = "Taipen" });             var p1 = from o in list select o;             Console.WriteLine(p1[0].Name);         }     }       public sealed class Person    {         public string Name { get; set; }         public int Age { get; set; }         public string Address { get; set; }     }       public static class PersonsExtension    {         public static Persons Select (this Persons source, Func selector)         {             return null;         }     }       public class Persons     {         private List _list = new List ();         public T this[int index]        {             get             {                 return _list[index];             }         }           public void Add(T item)        {             _list.Add(item);         }     } } static void Main(string[] args) {      FirstLinqHack.TestHacking.HackLinq(); } 將中斷點設在PersonsExtension.Select函式中,執行後會發現程式會停留在PersonsExtension.Select函式中,為何會如此?很簡單,C# 3.0只是單純的把LINQ Expression轉成object.Select(),基於Extension Method的優先權規則,以Persons 為參數的Select函式會被優先考慮,此處並無此函式,因此次要考慮的是以Persons 為參數的Extension Method:Select函式,所以控制權自然回到我們手中了。 (PS:LINQ TO SQL 對延伸LINQ的功能有更完善的架構,本節只是要驗證LINQ To Object Framework時,與編譯器間的關聯。)   效能的課題:LINQ To Object時的傳回值     從前面的Select、Where等Extension Method的宣告來看,LINQ To Object Framework所提供的函式傳回值多是實作了IEnumerable 介面的物件,圖4展現出當對字串陣列使用from xxx in xxx where xxx的LINQ Expression後的運作流程。 [ 圖4] 透過編譯器的轉換,LINQ Expression會變成string[].Where的函式呼叫,System.Linq.Enumerable.Where函式會建立一個WhereIterator物件,並於建立時將由編譯器轉換所建立出來的deleage(如where n = “2222”,會變成l => l == "2222",意指建立一個delegate,大概內容是bool generateFunc(string l) { return l == “2222”})及Source Enumerable Object,也就是string陣列傳入,當設計師操作此WhereIterator物件時,例如呼叫其MoveNext函式(foreach會呼叫此函式),WhereIterator將會逐一由Source Enumerable取出其內的元素,然後呼叫於建立WhereIterator時所傳入的delegate函式來決定此元素是否符合條件。了解流程後,就大略可以得知LINQ To Object Framework的效能了,這跟用foreach將元素一一比對放入另一個陣列中的效能相差無幾,只是LINQ Expression比這種做法簡潔多了。 (PS: 與IQueryable結合後的LINQ To ADO.NET Framework,效能就是LINQ Provider的課題了)   後記    下次筆者將針對LINQ Expression的語法做詳細的介紹,各位讀者們下次見了。  

    最新回复(0)