本節對委托、事件做以總結。 一、委托: 1、概念:先來說明變數和函數的概念,變數,以某個地址為起點的一段記憶體中所存儲的值,函數,以某個地址為起點的一段記憶體中存儲的機器語言指令。有了這2個概念以後,我們來看c++中的函數指針,函數指針就是指向這個函數的地址,函數指針所指向的類型就是函數在記憶體中的大小, ...
本節對委托、事件做以總結。
一、委托:
1、概念:先來說明變數和函數的概念,變數,以某個地址為起點的一段記憶體中所存儲的值,函數,以某個地址為起點的一段記憶體中存儲的機器語言指令。有了這2個概念以後,我們來看c++中的函數指針,函數指針就是指向這個函數的地址,函數指針所指向的類型就是函數在記憶體中的大小,有了這個起點和大小,函數指針就可以代替函數完成對函數的調用。在C#中,委托delegate就是對c++中函數指針做了一個升級,同樣它沒有直接調用方法採用的是間接調用,是一種類,所以也是一種數據類型。下麵舉一個簡單的例子,說明它是類。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp1 { class Program { static void Main(string[] args) { Action action = new Action(Method); Console.WriteLine(action.GetType().IsClass); } static void Method() { } } }
這裡我們用了C#類庫中自帶的Action委托,先不需要管它是什麼樣的委托,後面會介紹,然後調用Type類的IsClass屬性,返回true,則他就是一個類,所以它也是一種數據類型。可以看出,委托形成了一種動態調用代碼(方法)的結構,功能十分強大。
2、委托的一般使用:在聲明一個委托時,這個委托的參數就是一個方法名,這樣就可以把這個具體的委托當做參數傳入另一個方法,也就相當於把
這個委托中的方法當做參數傳入另一個方法,這個被傳入的方法分為2種:
(1)回調方法:無返回值,沒有返回值就說明他只是做了一些處理,至於被不被調用完全要看主調方法是否選擇調用它,這就和找工作一個道理,你發一份簡歷
出去,至於公司給不給你offer取決於公司。
(2)模(mu)板方法:有返回值,說明你所返回的東西會對調用者起一定的影響作用,有返回值一般也有參數,根據參數的不同返回不同的返回值,所以
它的作用對於調用者是一個模板。
Example1:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp1 { class Program { static void Main(string[] args) { Action action = new Action(Method); action.Invoke(); action(); } static void Method() { Console.WriteLine("Hello Delegate"); } } }
這裡用的是C#類庫中最常用的返回值為空並且無參的Action委托,Method方法無參無返回值,2種調用方式,第一種調用委托的invoke()方法,
第二種採用的是函數指針式的調用,都可以使用。
Example2:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp2 { class Program { static void Main(string[] args) { Action<string, int> action = new Action<string, int>(Method); action.Invoke("張三",18); action("李四",19); } static void Method(string name, int age) { Console.WriteLine($"我叫{name}今年{age}歲"); } } }
這裡用到了C#自帶的常見泛型委托Action<T>無返回值有參數,泛型這裡就當做一個類型就好,會在別的章節做詳細說明
Example3:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp2 { class Program { static void Main(string[] args) { Func<double, double, double> func = new Func<double, double, double>(Add); double result1 = func.Invoke(1.5,3.5); Console.WriteLine(result1); double result2 = func(2.5,4.5); Console.WriteLine(result2); } static double Add(double x, double y) { double result = x + y; return result; } } }
這裡用了C#類庫中常用的Func<T>委托,也是一個泛型委托,<>中最後一個是返回值結果,可以在vs的提示重載中看到。
Example4:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp2 { class Program { static void Main(string[] args) { Func<string> func = new Func<string>(FindName); SayHello(func); } static void SayHello(Func<string> FindDelegate) { Console.WriteLine($"Hello {FindDelegate()}"); } static string FindName() { return "小明"; } } }
這裡用了Func<T>只有返回值的情況,並將這個委托當做參數傳進了另一個方法,也就間接的把FindName這個方法當做參數傳入了SayHello這個方法。
3、下麵舉兩個比較貼近生活、委托和別的結合使用的典型事例。
Example1:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp3 { public delegate Product ProductDelegate(); public delegate void LogDelegate(Product product); class Program { static void Main(string[] args) { WrapProduct product = new WrapProduct(); Logger logger = new Logger(); WrapFactory wrapFactory = new WrapFactory(); ProductDelegate productDelegate1 = new ProductDelegate(product.GetToy); ProductDelegate productDelegate2 = new ProductDelegate(product.GetStationery); LogDelegate logDelegate = new LogDelegate(logger.Log); Box box1 = wrapFactory.GetBox(productDelegate1,logDelegate); Box box2 = wrapFactory.GetBox(productDelegate2,logDelegate); Console.WriteLine($"Product1 {box1.Product.Name} the price is {box1.Product.Price}"); Console.WriteLine($"Product2 {box2.Product.Name} the price is {box2.Product.Price}"); } } public class Product { public string Name { get; set; } public int Price { get; set; } } public class Box { public Product Product { get; set; } } public class WrapFactory { public Box GetBox(ProductDelegate productDelegate,LogDelegate logDelegate) { Box box = new Box(); Product product = productDelegate.Invoke(); if (product.Price>50) { logDelegate.Invoke(product); } box.Product = product; return box; } } public class WrapProduct { public Product GetToy() { Product product = new Product(); product.Name = "Toy"; product.Price = 100; return product; } public Product GetStationery() { Product product = new Product(); product.Name = "Stationery"; product.Price = 30; return product; } } public class Logger { public void Log(Product product) { Console.WriteLine($"Product {product.Name} created at {DateTime.Now.ToString()}"); } } }
delegate既然是類,那麼應該和類平級,放於類的外部。這裡用了自定義的委托,有返回值的委托ProductDelegate封裝的方法是WrapProduct製造產品類里的製造玩具和製造文具方法,無返回值的委托LogDelegate封裝的是Logger記錄日誌類里的Log日誌方法。首先做2個實體類,Product產品類,Box盒子類,盒子中放的就是產品,然後做一個包裝類,返回一個盒子,寫一個將產品包裝在盒子中的方法,這個方法的2個參數,是2個委托,一個用於創作產品一個當產品價格大於50的時候,就調用log方法記錄日誌,最後在main方法里開始實例化類並調用,自定義委托和C#類庫自帶的委托都可以使用,看個人喜好,C#自帶的就不用聲明委托了。
Example2:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp4 { class Program { static void Main(string[] args) { Func<int, int, int> func1 = new Func<int, int, int>((int a, int b) => { return a + b; }); var func2 = new Func<int,int,int>((a, b) => { return a * b; }); Calculate(func1,2,3); Calculate(func2,3,4); Calculate((x,y)=> { return x - y; },7,1); } static void Calculate<T>(Func<T,T,T> func,T x,T y) { T z = func.Invoke(x,y); Console.WriteLine(z); } } }
這裡用到了蠻多的小知識點,首先泛型函數和泛型委托,然後用到了lambda表達式,精簡的說一下lambda表達式:(T t)=>{expression; }小括弧里是參數,大括弧中是要寫的演算法,也就是方法體,當然不會寫太多,不然還不如寫一個方法就用不到lambda表達式了。已經知道,委托聲明是封裝一個方法,那麼就可以用lambda表達式代替方法,這就是把一個lambda表達式賦值給一個委托,C#中很多委托都會用到,所以第三次調用Calculate方法時,直接將lambda表達式當成參數傳進去,是不會報錯的。最後還有一個重要的點,就是泛型委托的類型參數推斷,在第二個委托func2中,C#根據傳入的參數推斷出泛型的具體類型是int,從而將代碼簡寫。
4、委托的抗變和協變
1、概念: .net 4.0中抗變和協變已經成熟了,主要分為2類,委托的和泛型的,此處只講委托的,泛型的後面會說明。委托所封裝的方法和聲明委托是所定義的類型不一定相同,這就產生了抗變和協變。
namespace ConsoleApp5 { class Father { } class Son : Father { } class Program { static void Main(string[] args) { Func<Father> func = new Func<Father>(Method); } static Son Method() { Son son = new Son(); return son; } } }
上面的是抗變:2個實體類Father父類,Son子類,繼承Father,委托聲明時,返回值為父類,調用的時候卻調用的是返回值為Son的方法,
也就是說抗變指的是委托所封裝的方法的返回值是聲明委托的返回值類型的子類。
namespace ConsoleApp5 { class Father { } class Son : Father { } class Program { static void Main(string[] args) { Action<Son> action = new Action<Son>(Method); } static void Method(Father father) { } } }
現在這個自然是協變,仍然一個父類一個子類,很明顯,協變指的是委托所封裝的方法的參數是聲明委托時參數的父類。
5、委托的高級使用:
主要講2個方面:多播委托以及委托的隱式非同步調用。
(1)多播委托:
通常的委托,一個委托封裝一個方法,多播委托中可以一個委托封裝多個方法,這些方法通常都是void的,但是不為空也可以,不會報錯,實例如下:
namespace ConsoleApp6 { class Program { static void Main(string[] args) { var func = new Func<int, int, int>(M1); func += M2; func += M3; func(3,3); Console.WriteLine(func(3,3)); } static int M1(int x, int y) { Console.WriteLine(x+y); return x + y; } static int M2(int x, int y) { Console.WriteLine(x - y); return x - y; } static int M3(int x, int y) { Console.WriteLine(x*y); return x * y; } } }
可以看到確實沒有報錯,但是它最後的返回值是9,也就是說調用多播委托以後他最後的返回值是最後一個方法的返回值,所以有返回值的方法一般不用於多播委托,來看一個正常的例子。
namespace ConsoleApp7 { class Program { static void Main(string[] args) { var action = new Action(M1); action += M2; action += M3; action.Invoke(); action -= M2; action.Invoke(); } static void M1() { Console.WriteLine("M1 is invoked"); } static void M2() { Console.WriteLine("M2 is invoked"); } static void M3() { Console.WriteLine("M3 is invoked"); } } }
這裡用+=和-=將方法逐一封裝在同一個委托里,實現了只需要調用一次委托就調用了所有方法的功能。
那這裡的底層實現是什麼呢,先舉實例:
namespace ConsoleApp8 { class Program { static void Main(string[] args) { var action1 = new Action(M1); var action2 = new Action(M2); var action3 = new Action(M3); Action action = null; action = (Action)Delegate.Combine(action,action1); action = (Action)Delegate.Combine(action,action2); action = (Action)Delegate.Combine(action,action3); action(); Console.WriteLine(); action = (Action)Delegate.Remove(action,action2); action(); } static void M1() { Console.WriteLine("M1 is invoked"); } static void M2() { Console.WriteLine("M2 is invoked"); } static void M3() { Console.WriteLine("M3 is invoked"); } } }
從上面的例子中可以看出,+=和-=的具體實現使用Delegate的Combine和Remove方法,來增加或刪除委托中的方法。
(2)先來看一下顯示非同步調用:
namespace ConsoleApp9 { class Program { static void Main(string[] args) { Thread thread1 = new Thread(new ThreadStart(M1)); Thread thread2 = new Thread(new ThreadStart(M2)); thread1.Start(); thread2.Start(); } static void M1() {} static void M2() {} } }
這裡可以用到了線程,可以看到ThreadStart是一個委托。
namespace ConsoleApp10 { class Program { static void Main(string[] args) { Task task = new Task(new Action(M1)); task.Start(); } static void M1() { } } }
這裡用Task也可以,也是線程中的東西關於task會在以後詳細說明,可以看到參數也是一個委托。
下麵是隱式非同步調用的例子:
namespace ConsoleApp11 { class Program { static void Main(string[] args) { Calculator calculator = new Calculator(); Func<int, int, int> func = new Func<int, int, int>(calculator.Add); IAsyncResult asyncResult = func.BeginInvoke(2,3,null,null); Console.WriteLine($"結果是{func.EndInvoke(asyncResult)}"); Console.WriteLine("計算完成"); } } public class Calculator { public int Add(int a, int b) { return a + b; } } }
隱式非同步調用的底層機制就是多線程,而委托中的BeginInvoke方法恰好會生成分支線程,所產生的信息可以通過IAsyncResult接受,產生的返回值調用EndInvoke方法即可,註:BeginInvoke方法的參數是一個AsyncCallBack委托,主要用來作為回調函數也就是你調完方法以後還需要做什麼,如果不需要傳入null就可以了。
到此委托部分結束,事件會在下一節總結。 2018-08-17 10:31:56