C#沉澱-委託

什麼是委託
可以認為委託是持有一個或多個方法的物件。委託可以被執行,執行委託時委託會執行它所“持有”的方法
程式碼示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForDelegate { //使用關鍵字delegate宣告委託型別 //委託是一種型別,所以它與類屬於同一級別 //注意:這裡是委託型別,而不是委託物件 delegate void MyDel(int value); class Program { void PrintLow(int value) { Console.WriteLine("{0} - Low Value", value); } void PrintHigh(int value) { Console.WriteLine("{0} - High Value", value); } static void Main(string[] args) { //例項化Program類,以訪問PrintLow和PrintHigh方法 Program program = new Program(); //宣告一個MyDel型別的委託 MyDel del; //建立隨機數 Random rand = new Random(); int randomvalue = rand.Next(99); //使用三目運算子根據當前隨機數的值來建立委託物件 del = randomvalue < 50 ? new MyDel(program.PrintLow) : new MyDel(program.PrintHigh); //執行委託 del(randomvalue); Console.ReadKey(); } } }
從上例可以看出,使用委託的首先得通過關鍵字 delegate 宣告一個委託型別,這個委託型別包括返回值、名稱、簽名;當型別宣告好以後,需要通過 new 來建立委託物件,建立物件時的引數是一個方法,這個方法的簽名和返回型別必須與該委託型別定義的簽名一致;呼叫委託時,直接通過例項化的委託物件名,並提供引數即可,然後委託會執行在其所持有的方法
委託與類
委託和類一樣,是一種使用者自定義的型別;不同的是類表示的是資料和方法的集合,而委託持有一個或多個方法,以及一系列預定義操作
委託的使用步驟
- 宣告一個委託型別
- 使用該委託型別宣告一個委託變數
- 建立委託型別的物件,把它賦值給委託變數;委託物件中包括指向某個方法的引用,此方法和委託型別定義的簽名與返回型別需要一致
- 增加更多的方法(可選)
- 像呼叫方法一樣呼叫委託(委託中的包含的每一個方法都會被執行)
delegate的原則
delegate相當於一個包含有序方法列表的物件,這些方法都具有相同的簽名和返回型別
- 方法的列表稱為 呼叫列表
- 委託儲存的方法可以來自任何類或結構,只要它們在以下兩點匹配:
- 委託的返回型別
- 委託的簽名(包括ref和out修飾符)
- 呼叫列表中的方法可以是靜態方法也可以是例項方法
- 在呼叫委託的時候,會呼叫列表中的所有方法
宣告委託型別
如下, delegate 關鍵字開關,然後是返回型別,再定義名稱與簽名
delegate void MyDel(int vallue);
返回型別與簽名指定了委託接受的方法形式
注意:委託型別是沒有方法主體的
建立委託物件
使用 new 運算子建立物件
MyDel del = new MyDel(object.Func); //object.Func是個例項方法 Mydel _del = new MyDel(Object.Func); //Object.Func是個靜態方法
使用快捷語法建立物件
MyDel del = object.Func; //object.Func是個例項方法 Mydel _del = Object.Func; //Object.Func是個靜態方法
這種語法是能夠工作是因為在方法名稱和其相應的委託型別之間存在隱式的轉換
建立委託物件後會將指定的方法加入到委託的呼叫列表中
由於委託是引用型別,可以通過賦值來改變包含在委託變數中的引用,如下:
MyDel del; del = new MyDel(object.FuncA); //建立第一個物件 del = new MyDel(object.FuncB); //建立第二個物件
由於第二個物件也賦值給了變數del,因此del所引用的第一個物件將被垃圾回收器回收
組合委託
//建立兩個委託 MyDel del_A = new MyDel(object.FuncA); Mydel del_B = new MyDel(object.FuncA); //組合委託 MyDel del_C = del_A + del_B;
當將del_A與del_B通過 +
進行組合後,會返回一個新的委託物件,該物件將del_A與del_B中的方法呼叫列表組合到新的物件裡,該新物件賦值給變數del_C,所以執行del_C的時候,會執行del_A與del_B中所儲存的方法object.FuncA和object.FuncA
委託新增多個方法
MyDel del = object.FuncA; //建立並初始化委託物件 del += object.FuncB; //增加方法 del += object.FuncC; //增加方法
通過 +=
符號為委託物件新增更多方法,上例中,del物件不儲存了三個方法,在執行del時,這三個方法會被依次呼叫
注意,在使用+=為委託物件新增新的方法時,實際上是建立了一個新的委託物件(原物件的副本)
移除委託方法
del -= object.FuncB; //移除方法 del -= object.FuncC; //移除方法
通過 -=
來將委託 呼叫列表 中已儲存的方法,移除動作是從呼叫列表的最後一個方法開始匹配,一次只會移除一條匹配的方法,如果呼叫列表中不存在該方法,則沒有任何效果;如果試圖呼叫一個空的委託則會發生異常
注意,在使用-=為委託物件移除方法時,實際上是建立一個新的委託物件(原物件的副本)
呼叫委託
呼叫委託就像呼叫方法一樣
示例: MyDel型別參考上面的定義
MyDel del = object.FuncA; //建立並初始化委託物件 del += object.FuncB; //增加方法 del += object.FuncC; //增加方法 //呼叫委託 del(55);
引數 55
會在呼叫委託物件時依次傳遞給儲存的方法
一個完整的委託示例程式碼
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForDelegate { class Program { //定義委託型別 delegate void PrintFunction(string txt); //測試類中定義三個方法 class Test { public void PrintA(string txt) { Console.WriteLine("printA:{0}", txt); } public void PrintB(string txt) { Console.WriteLine("printB:{0}", txt); } public static void PrintC(string txt) { Console.WriteLine("printC:{0}", txt); } } static void Main(string[] args) { Test test = new Test(); PrintFunction pf; //例項化並建立委託物件 pf = test.PrintA; //為委託物件增加方法 pf += test.PrintB; pf += Test.PrintC; pf += test.PrintA; //新增一個重複的方法 //通過與null比較,確認委託物件中儲存了方法 if (pf != null) pf("Hello"); else Console.WriteLine("pf是個空委託!"); Console.ReadKey(); } } }
呼叫帶有返回值的委託
如何委託有返回值,並且呼叫列表中有一個以上的方法,那麼將使用最後一個方法的返回值,之前方法的返回值被忽略
示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForDelegate { class Program { //定義委託型別 delegate int DelFunction(); //測試類中定義三個方法 class Test { int IntValue = 0; public int FuncA() { return IntValue += 1; } public int FuncB() { return IntValue += 10; } } static void Main(string[] args) { Test test = new Test(); DelFunction df; df = test.FuncA; df += test.FuncB; //最終返回值的是11 if (df != null) Console.WriteLine("返回值:"+df()); else Console.WriteLine("pf是個空委託!"); Console.ReadKey(); } } }
具有引用引數的委託
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForDelegate { //定義委託型別 delegate void MyDel(ref int x); class Program { static void Add1(ref int x) { x += 1; } static void Add2(ref int x) { x += 2; } static void Main(string[] args) { Program program = new Program(); MyDel del = Add1; del += Add2; //ref會將x當作引用值傳遞給委託方法 int x = 5; del(ref x); Console.ReadKey(); } } }
在呼叫Add1方法時,x = 5+1,再呼叫Add2方法時,不是x = 5+2而是x = 6 +2
參考:ref按引用傳遞引數
在方法的引數列表中使用 ref
關鍵字時,它指示引數按引用傳遞,而非按值傳遞。 按引用傳遞的效果是,對所呼叫方法中引數進行的任何更改都反映在呼叫方法中。 例如,如果呼叫方傳遞本地變量表達式或陣列元素訪問表示式,所呼叫方法會替換 ref 引數引用的物件,然後,當該方法返回時,呼叫方的本地變數或陣列元素將開始引用新物件
匿名方法
匿名方法是在初始化委託時內聯宣告的方法
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForDelegate { //定義委託型別 delegate void MyDel(ref int x); class Program { static void Add1(ref int x) { x += 1; } static void Add2(ref int x) { x += 2; } static void Main(string[] args) { Program program = new Program(); //採用匿名方法形式代替具名方法 MyDel del = delegate(ref int y) { y += 3; }; del += Add1; del += Add2; //ref會將x當作引用值傳遞給委託方法 int x = 5; del(ref x); Console.ReadKey(); } } }
在宣告委託變數時作為初始化表示式,或在為委託增加事件時使用
語法解析
以關鍵字 delegate 開頭;後跟小括號提供引數;再後跟{}作為語句塊
delegate (Parameters) {ImplementationCode}
delegate (int x) { return x;}
示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForDelegate { //定義委託型別 delegate void MyDel(ref int x); class Program { static void Add1(ref int x) { x += 1; } static void Add2(ref int x) { x += 2; } static void Main(string[] args) { Program program = new Program(); //採用匿名方法形式代替具名方法 MyDel del = delegate(ref int y) { y += 3; }; del += Add1; del += Add2; //匿名方法未使用任何引數,簡化形式 del += delegate{int z = 10;}; //ref會將x當作引用值傳遞給委託方法 int x = 5; del(ref x); Console.ReadKey(); } } }
如果定義一個帶有params形式的引數,在使用匿名方法的時候可以省略params關鍵字以簡化程式碼
示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CodeForDelegate { //定義一個帶有params形式引數的委託型別 delegate void DelFunction(int x, params int[] z); class Program { static void Main(string[] args) { Program program = new Program(); // 關鍵字params被忽略(省略關鍵字以簡化) DelFunction df = delegate(int x, int[] y) { ... }; Console.ReadKey(); } } }
Lambda表示式
Lambda可以簡化匿名方法,語法形式如下:
(引數) => {語句塊} // => 讀作 gose to
- 引數中的型別可以省略
- 如果只有一個引數,圓括號可以省略
- 如果沒有引數,圓括號不可以省略
- 語句塊如果只有一行程式碼,花括號可以省略
示例:
MyDel del = delegate(int y) { return y += 3; }; //匿名方法 MyDel del1 = (int y) => {return y += 3;} // Lambda表示式 MyDel del2 = (y) => {return y += 3;} // 省略引數型別 MyDel del3 = y => y += 3; // 省略圓括號和花括號,雖然沒有return,但仍會返回y的值