十二、C# 委托與Lambda表達式(匿名方法的另一種寫法)


委托與Lambda表達式
 
1、委托概述
2、匿名方法
3、語句Lambda
4、表達式Lambda
5、表達式樹
 
一、委托概述
相當於C++當中的方法指針,在C#中使用delegate 委托來提供相同的功能,
它將方法作為對象封裝起來,允許在"運行時"間接地綁定一個方法調用。
聲明的委托相當於一種自定義的數據類型。
1、背景
冒泡排序
 1     static class SimpleSort1
 2     {
 3         public static void BubbleSort(int[] items)
 4         {
 5             int i = 0, j = 0, temp = 0;
 6             if (items == null)
 7             {
 8                 return;
 9             }
10             for (i = items.Length - 1; i >= 0; i--)
11             {
12                 for (j = 1; j <= i; j++)
13                 {
14                     if (items[j - 1] > items[i])
15                     {
16                         temp = items[j - 1];
17                         items[j - 1] = items[i];
18                         items[i] = temp;
19                     }
20                 }
21  
22             }
23         }
24     }

 

如果需要提供升序和降序兩個功能選擇,可以加上參數SortType來區別
 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5  
 6             int[] arr = new int[] { 1, 2, 5, 645, 4, 6, 73, 5, 3, 6, 7, 66 };
 7             SimpleSort1.BubbleSort(arr, SortType.Ascending);
 8             string str = "";
 9             for (int i = 0; i < arr.Length; i++)
10             {
11                 str += arr[i] + ",";
12  
13             }
14             Console.WriteLine(str);
15  
16             str = "";
17             SimpleSort1.BubbleSort(arr, SortType.Descending);
18             for (int i = 0; i < arr.Length; i++)
19             {
20                 str += arr[i] + ",";
21  
22             }
23             Console.WriteLine(str);
24             Console.ReadLine();
25  
26         }
27     }
28     enum SortType
29     {
30         Ascending,
31         Descending
32     }
33     static class SimpleSort1
34     {
35         public static void BubbleSort(int[] items, SortType sorttype)
36         {
37             int i = 0, j = 0, temp = 0;
38             if (items == null)
39             {
40                 return;
41             }
42             for (i = items.Length - 1; i >= 0; i--)
43             {
44                 for (j = 1; j <= i; j++)
45                 {
46                     switch (sorttype)
47                     {
48                         case SortType.Ascending:
49                             if (items[j - 1] > items[i])
50                             {
51                                 temp = items[j - 1];
52                                 items[j - 1] = items[i];
53                                 items[i] = temp;
54                             }
55                             break;
56                         case SortType.Descending:
57                             if (items[j - 1] < items[i])
58                             {
59                                 temp = items[j - 1];
60                                 items[j - 1] = items[i];
61                                 items[i] = temp;
62                             }
63                             break;
64  
65                     }
66  
67                 }
68  
69             }
70  
71  
72         }
73     }
74  

 

如果想要按字母、隨機或者按照其他方式來排序, 可以通過增加一個SortType參數
BubbleSort()方法及其對應的SortType值的數量很快會變量非常之多。
 
2、委托數據類型
為了減少重復代碼的數量,可以將比較方法作一個參數
傳給BubbleSort()方法,此外,為了能將方法作為參數傳遞,必須要有一個能夠
表示方法的數據類型---也就是要有一個委托。
 
大致使用步驟:聲明一個委托數據類型,聲明的一個新的委托相當於一個數據類型。
可以用來作為形式參數的類型,在方法調用的時候,賦值一個與此委托相當簽名的方法。
 
 
3、委托的內部機制
C#將所有委托定義成間接派生於System.Delegate
委托類型的對象模型:待查。
所有委托都是不可變的。更改“委托”要求實例化一個新委托,並在新委托中包含要修改
的地方。
 
4、委托類型的定義
 1   class DelegateSample
 2     {
 3         public delegate bool ComparisonHandler(int first, int second);
 4         //相當於創建了一個數據類型:DelegateSample.ComparisonHandler
 5 //因為它被定義成嵌套在DelegateSample中的一個類型。
 6  
 7     }
 8 雖然所有委托數據類型都間接從System.Delegate派生,但C#編譯器並不允許定義一個直接或間接
 9 從System.Delegate派生的類。
10  
11     class Program
12     {
13         static void Main(string[] args)
14         {
15  
16             int[] arr = new int[] { 1, 32, 5, 645, 4, 6, 73, 5, 36, 61, 7, 66 };
17             string str = "";
18             //調用方法時,將指定的函數作為實際參數使用。使用方法來創建一個委托變量,委托是一個引用類型,但不必
19             //用new來實例化它。直接傳遞名稱,而不是顯式實例化,這是自C#2.0開始支持的一個新語法,稱為委托推斷 delegate interface
20             //采用這個語法,編譯器將根據方法名來查找方法簽名,並驗證它同方法的參數類型匹配。
21             SimpleSort1.BubbleSort(arr, SimpleSort1.GreaterThan);          
22             for (int i = 0; i < arr.Length; i++)
23             {
24                 str += arr[i] + ",";
25  
26             }
27             Console.WriteLine(str);
28  
29             str = "";
30             SimpleSort1.BubbleSort(arr, SimpleSort1.LonwerThan);
31             for (int i = 0; i < arr.Length; i++)
32             {
33                 str += arr[i] + ",";
34  
35             }
36             Console.WriteLine(str);
37  
38             str = "";
39             SimpleSort1.BubbleSort(arr, SimpleSort1.CharThan);
40             for (int i = 0; i < arr.Length; i++)
41             {
42                 str += arr[i] + ",";
43  
44             }
45             Console.WriteLine(str);
46  
47             Console.ReadLine();
48  
49  
50  
51         }
52     }
53  
54     static class SimpleSort1
55     {
56         //使用委托數據類型 聲明一個變量作為形式參數
57         public static void BubbleSort(int[] items, DelegateSample.ComparisonHandler compareMethod)
58         {
59             int i = 0, j = 0, temp = 0;
60             if (items == null)
61             {
62                 return;
63             }
64             for (i = items.Length - 1; i >= 0; i--)
65             {
66                 for (j = 1; j <= i; j++)
67                 {
68                     if (compareMethod(items[j - 1], items[i]))
69                     {
70                         temp = items[j - 1];
71                         items[j - 1] = items[i];
72                         items[i] = temp;
73                     }
74                 }
75             }
76         }
77  
78  
79         //以下四個函數都與數據類型DelegateSample.ComparisonHandler(委托) 具有同樣的簽名
80         public static bool GreaterThan(int first, int second)
81         {
82             return first > second;
83         }
84         public static bool LonwerThan(int first, int second)
85         {
86             return first < second;
87         }
88         public static bool CharThan(int first, int second)
89         {
90             int flag = (first.ToString()).CompareTo(second.ToString());
91             return (flag > 0) ? true : false;
92         }
93     }
94     class DelegateSample
95     {
96         public delegate bool ComparisonHandler(int first, int second);
97         //相當於創建了一個數據類型:DelegateSample.ComparisonHandler
98  
99     }

 

 
二、匿名方法
C#2.0 和更高的版本還支持不念舊惡名為匿名方法的特性。
所謂匿名方法,就是沒有實際方法聲明的委托實例。
1             DelegateSample.ComparisonHandler compareMethod;
2             compareMethod =
3                 delegate(int first, int second)
4                 {
5                     return first > second;
6                 };
7             SimpleSort1.BubbleSort(arr, compareMethod);

 

 
在這個上下文中,delegate關鍵字指定了一個“委托字面值“類型,這類似用雙引號來指定一個
字符串字面值。
同理可以在不使用compareMethod 變量的前提下直接使用匿名方法當作實際參數來使用。

 

1             SimpleSort1.BubbleSort(arr,
2                 delegate(int first, int second)
3                 {
4                     return first > second;
5                 }
6                 );

 

在任何情況下,匿名方法的參數和返回值類型都必須兼容於相對應的委托類型。
自C#2.0開始,可以利用匿名方法這一新特性來聲明一個沒有名字的方法,該方法將
自動被轉換成一個委托。
無參數的匿名方法,可以省略() ,但返回類型仍然需要與委托的返回類型兼容。
 
三、系統定義的委托:Func<> Action<>
在.NET3.5(C#3.0)中,存在一系列名為Action和Func的泛型委托。(委托模板)
System.Func代表有返回類型的委托,而System.Action代表無返回類型的委托。
這些委托全是泛型的,因此可以直接使用它們而不用自定義一個委托。
如:
系統自帶的
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);這只是其中的一種
更多可以參考源程序集。
可以如此使用
1             System.Func<int, int, bool> compareMethodFun;
2             compareMethodFun =
3                 delegate(int first, int second)
4                 {
5                     return first > second;
6                 };

 

 
代碼可以不必聲明一個ComparisonHandler委托類型,而是直接使用Func<int, int, bool> 這個泛型類型委托
來聲明一個委托類型的實例。
注:Func的最后一個類型參數總是委托的返回類型,在它之前的類型參數
則依次對應於委托參數的類型。
對於那些沒有返回類型而且不接受參數的委托,應該使用System.Action或者某個泛型類型。
 
 
比如自定義的委托類型,可以讓我們明確知道該委托的用途,相反
Func<int, int, bool> 除了讓我們明白方法的簽名之外,就不能再提供其他有意義的信息了。
 
注:這些只有安裝.NET3.5的客戶端才能使用此功能。
 
注意,即使可以且一個Func泛型委托來代替一個顯式定義的委托,兩者的類型也不兼容。
不能將一個委托類型賦給另一個委托類型的變量,即使類型參數匹配。
 
通過C#4.0 添加的對協變性和逆變性的支持,確實可以在它們之間進行一定程序的轉型。
 
但是,假如代碼中使用某個委托類型能使編碼變得更簡單,那么仍然應該聲明委托類型。
 
void Action<in T>(T arg) 有一個in類型的參數修飾符,所以支持逆變性
所以可以將Action<object>類型的一個對象賦給Action<string>類型的一個變量。
同理out
 
 1             Action<Object> broadAction = delegate(Object o)
 2             {
 3                 Console.WriteLine(o);
 4             };
 5             Action<String> narrowAction = broadAction;
 6  
 7             Func<string> narrowFunction = delegate()
 8             {
 9                 return Console.ReadLine();
10             };
11  
12             Func<Object> broadFunction = narrowFunction;

 

同理,可以in out同時發生。
1               Func<Object, String> narrowFunction = delegate(Object obj)
2             {
3  
4                 return obj.ToString();
5             };
6  
7             Func<String, Object> broadFunction = narrowFunction;

 

在這些泛型委托中對協變性和逆變性 的需求是C#現在添加這個功能的一個關鍵原因。
 
四、Lambda表達式
C#3.0中引入的Lambda表達式是比匿名方法更加簡潔的一種匿名函數語法。
這里的匿名函數泛指Lambda表達式和匿名方法。
Lambda表達式本身划分為兩種類型:語句Lambda和表達式Lambda。
與匿名函數有關的術語,待查。
注:所有匿名函數都是不可變的。
 
1、語句Lambda
語句Lambda是C#3.0為匿名方法提供的一種簡化語法。
這種語法不包含delegate關鍵字,但添加了Lambda運算符  =>  。
 
1             SimpleSort1.BubbleSort(arr,
2                 (int first, int second) =>
3                 {
4       //可以有多個語句
5                     return first > second;
6                 }
7                 );

 

查看含有Lambda運算符的代碼時,可以將這個運算符理解成"用於"兩字。
如以上則理解為first second用於返回 first>second。
語句Lambda還允許通過"類型參數推斷"來進一步簡化語法。可以不顯式聲明參數的
數據類型,只要編譯器能推斷出類型,語句Lambda就允許省略參數類型。
1             SimpleSort1.BubbleSort(arr,
2                 (first, second) =>
3                 {
4                     return first > second;
5                 }
6                 );

 

通常,只要編譯器能推斷出參數類型,或者能將參數類型隱式轉換成期望的類型,語句
Lambda就不需要參數類型。
只要語句Lambda包含了一個類型,所有類型都要加上。
即使是無參數的語句Lambda,也需要輸入一對空白的圓括號。
 
圓括號規則的一個例外是,如果編譯器能推斷出數據類型,而且只有一個輸入參數的時候,語句
Lambda可以不帶圓括號。
如:
1             IEnumerable<Process> processes = Process.GetProcesses().Where(
2                 process => { return process.WorkingSet64 > (2 ^ 30); });

 

 
以上代碼返回的是對物理內存占用超過1GB的進程的一個查詢。
雖然一個語句Lambda允許包含任意數量的語句,但在它的語句塊中,一般只使用兩三條語句。
 
2、表達式Lambda
語句Lambda含有一個語句塊,所以可以包含多個語句或者零個語句。
表達式Lambda只有一個表達式,沒有語句塊。其它一致。
如:
1             SimpleSort1.BubbleSort(arr, (first, second) => first > second);
2             SimpleSort1.BubbleSort(arr, (int first, int second) => first > second);

 

語句Lambda和表達式Lambda的區別在於,語句Lambda在Lambda運算符的右側有一個語句塊,用{}包含。
而表達式Lambda只有一個表達式(沒有return語句及大括號)。
 
注:括號及參數類型的省略同語句Lambda
 
3、Lambda表達式和匿名方法的內部機制
 
Lambda表達式(和匿名方法)並非CLR內部的固有構造,它們的實現是C#編譯器在編譯時生成的。
 
Lambda表達式為“以內嵌方式聲明的委托”模式提供了一個對應的C#語言構造。
這樣一來,C#編譯就會為這個模式生成實現代碼。
在CIL中,一個匿名方法被轉換成一個單獨、由編譯器
內部聲明的靜態方法,這個靜態方法再實例化成一個委托,並作為參數傳遞。
 
4、外部變量
在Lambda表達式(包括參數)的外部聲明,但在Lambda表達式內部訪問的局部變量稱為該
Lambda表達式的外部變量。
this也是一個外部變量,外部變量被匿名函數捕捉(訪問)后,在匿名函數的委托被銷毀之前,該
外部變量將一直存在。
被捕捉的變量的生存期至少和捕捉它的最長命的委托對象一樣長。
類似閉包。
 
外部變量的CIL實現 。
被捕捉的局部變量被當作一個實例字段來實現,從而延長了其生命周期。
對局部變量的所有引用都被重新編寫成對那個字段的引用。
變量允許在不改變表達式簽名的前提下,將數據從表達戒指一次調用傳遞到下一次調用。
 
5、表達式樹
 
如果一種Lambda表達式代表的是與表達式有關的數據,而不是編譯好的代碼,這種Lambda表達式就是
"表達式樹"。
1、Lambda表達式作為數據使用
由於表達式樹代表的是數據而非編譯好的代碼,所以可以把數據轉換成一種替代格式。
例如,可以把它從表達式數據轉換成數據庫中執行的SQL代碼。
但,表達式樹並非只能轉換成SQL語句。
 
2、表達式樹作為對象圖使用
表達式樹轉換成的數據是一個對象圖,它由System.Linq.Expressions.Expression表示。
 
 
3、Lambda表達式和表達式樹的比較
 
在編譯時,Lambda表達式在CIL中被編譯成一個委托,而表達式樹被編譯成System.Linq.Expressions.Expression
類型的一個數據結構。
 
可通過System.Linq.Enumerable 與 System.Linq.Queryable這兩個類的進行比較.
它們分別實現了IEnumerable和IQueryable集合接口。例如 Where()。
 
4、解析表達式樹
由於Lambda表達式沒有與生俱來的類型,所以將Lambda表達式賦給System.Linq.Expressions.Expression<TDelegate>
會創建表達式樹,而不是委托。
1  System.Linq.Expressions.Expression<Func<int, int, bool>> expression;
2 expression = (x, y) => x > y;
 
表達式樹是一個數據集合,而通過遍歷數據,就可以將數據轉換成另一種格式。
有多個表達式類型,
如:
System.Linq.Expressions.BinaryExpression
System.Linq.Expressions.ConditionalExpression
System.Linq.Expressions.LambdaExpression
System.Linq.Expressions.MethodCallExpression
System.Linq.Expressions.ParameterExpression
System.Linq.Expressions.ConstantExpression
每個類都派生自System.Linq.Expressions.Expression
 
通常,語句Lambda和表達式Lambda可以互換使用。然則,不能將語句Lambda轉換成"表達式樹"。
只能使用表達式Lambda語法來表示"表達式樹"。
 
小結:
利用委托,可以傳遞一組能在不同位置調用的指令,這些指令不是立即調用的,而是在完成編碼之后,再在其他位置調用。
C#2.0 中引入了"匿名方法"
C#3.0 中引入了Lambda表達式的概念。
Lambda表達式語法取代(但並非消除)了C#2.0 的匿名語法。
不管哪種語法,程序員都可以利用它們直接將一組指令賦一個變量,而不必顯式地定義
一個包含這些指令的方法。
這使得程序員可以在方法內部動態地編寫指令----這是一個很強大的概念,
它通過一個稱為LINQ(語言集成查詢)的API大幅簡化了集合編程。
表達式樹的概念,描述了它們如何編譯成數據來表示一個Lambda表達式,而不是表示委托的具體實現。
利用這一特性,LINQ to SQL 和 LINQ to XML等庫能解釋表達式樹,
並在CIL之外的上下文中使用它。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM