C#语言入门详解---委托(刘铁猛)
委托:函數指針的升級版,可以類比C語言中的函數指針進行理解
變量的本質就是以變量名所對應的內存地址為起點的一段內存,這段內存中存儲的就是變量的數據,這段內存的大小由變量的數據類型決定。
函數代表算法,函數的本質是以函數名所對應的內存地址為起點的一段內存中,這段內存中存儲的不是某個值,而是一組機器語言的指令,CPU就是按照這組指令一條一條執行完成這段函數中所包含的算法。
無論是數據還是算法都是保存在內存中的,變量是用來尋找數據的地址,函數是用來尋找算法的地址。
C#通過委托這種數據類型保留了與C/C++語言中函數指針對應的功能。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace 委托_劉鐵猛 {class Program{static void Main(string[] args){Caculator caculator = new Caculator();Action action = new Action(caculator.Report);Func<int, int, int> func1 = new Func<int, int, int>(caculator.Add);Func<int, int, int> func2 = new Func<int, int, int>(caculator.Sub);caculator.Report(); //直接調用action.Invoke(); //間接調用1action(); //間接調用2int a = 200;int b = 300;int c = 0;//直接調用c = caculator.Add(a, b);Console.WriteLine(c);c = caculator.Sub(a, b);Console.WriteLine(c);//間接調用1c = func1.Invoke(a, b);Console.WriteLine(c);c = func2.Invoke(a, b);Console.WriteLine(c);//間接調用2c = func1(a, b);Console.WriteLine(c);c = func2(a, b);Console.WriteLine(c);}}class Caculator{public void Report(){Console.WriteLine("I have 3 methods");}public int Add(int a, int b){int result = a + b;return result;}public int Sub(int a, int b){int result = a - b;return result;}} }自定義委托時一定要注意:
1)委托與封裝的方法必須類型兼容,返回值和參數的數據類型和參數數目需一致
2)委托是一種類,因此將其聲明在名稱空間里面時其和其它類處于同一級。C#中允許嵌套類,即在一個類中可以嵌套另一個類,因此若將委托放到某一個類中,則委托就變成了該類中的嵌套類
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace 委托_劉鐵猛 {public delegate double Calc(double x, double y); //定義委托class Program{static void Main(string[] args){Calculator calculator = new Calculator();Calc calc1 = new Calc(calculator.Add);Calc calc2 = new Calc(calculator.Sub);Calc calc3 = new Calc(calculator.Mul);Calc calc4 = new Calc(calculator.Div);double a = 100;double b = 200;double c = 0;c = calc1.Invoke(a, b);//等價于c = calc1(a, b);Console.WriteLine(c);c = calc2.Invoke(a, b);Console.WriteLine(c);c = calc3.Invoke(a, b);Console.WriteLine(c);c = calc4.Invoke(a, b);Console.WriteLine(c);Console.WriteLine("-------------------------");Calc[] calcArray = new Calc[4];calcArray[0] = calculator.Add;calcArray[1] = calculator.Sub;calcArray[2] = calculator.Mul;calcArray[3] = calculator.Div;foreach (var item in calcArray){c = item.Invoke(a, b);//等價于c = item(a, b);Console.WriteLine(c);}}}class Calculator{public double Add(double x, double y){return x + y;}public double Sub(double x, double y){return x - y;}public double Mul(double x, double y){return x * y;}public double Div(double x, double y){return x / y;}} }模板方法:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace 委托_劉鐵猛 {public delegate double Calc(double x, double y); //定義委托class Program{static void Main(string[] args){ProductFactory productFactory = new ProductFactory();WrapFactory wrapFactory = new WrapFactory();Func<Product> func1 = new Func<Product>(productFactory.MakePizza);Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);Box box1 = wrapFactory.WrapProduct(func1);Box box2 = wrapFactory.WrapProduct(func2);Console.WriteLine(box1.Product.Name);Console.WriteLine(box2.Product.Name);}}class Product{public string Name { get; set; }}class Box{public Product Product { get; set; }}class WrapFactory{/// <summary>/// 模板方法,委托的調用getProduct是可以修改的地方,傳入不同的getProduct可以實現/// 不同的產出產品,不同的產品不用再修改WrapProduct中的方法/// </summary>/// <param name="getProduct"></param>/// <returns></returns>public Box WrapProduct(Func<Product> getProduct){Box box = new Box(); //準備一個BoxProduct product = getProduct.Invoke(); //獲取一個產品box.Product = product; //把產品裝到Box里面return box; //返回Box}}class ProductFactory{public Product MakePizza(){Product product = new Product();product.Name = "Pizza";return product;}public Product MakeToyCar(){Product product = new Product();product.Name = "Toy Car";return product;}} }參照如上代碼,體會使用模板方法的好處是:Product類、Box類、WrapFactory類都不用修改,只需要擴展ProductFactory類中的產品就可以生產不同的產品。不管是生產哪種產品的方法,只要將該方法封裝到委托類型的對象里傳給模板方法,調用模板方法時就可以將產品包裝成箱子再交還回來,這樣可以最大限度的實現代碼的重復使用。代碼的復用不但可以提高工作效率,還可以減少程序Bug的引入,良好的復用結構是所有優秀軟件所追求的共同目標之一。
回調方法:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace 委托_劉鐵猛 {public delegate double Calc(double x, double y); //定義委托class Program{static void Main(string[] args){ProductFactory productFactory = new ProductFactory();WrapFactory wrapFactory = new WrapFactory();Func<Product> func1 = new Func<Product>(productFactory.MakePizza);Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);Logger logger = new Logger();Action<Product> log = new Action<Product>(logger.Log);Box box1 = wrapFactory.WrapProduct(func1, log);Box box2 = wrapFactory.WrapProduct(func2, log);Console.WriteLine(box1.Product.Name);Console.WriteLine(box2.Product.Name);}}class Logger{public void Log(Product product){Console.WriteLine("Product {0} created at {1}. Price is {2}",product.Name, DateTime.UtcNow, product.Price);}}class Product{public string Name { get; set; }public double Price { get; set; }}class Box{public Product Product { get; set; }}class WrapFactory{/// <summary>/// 模板方法,委托的調用getProduct是可以修改的地方,傳入不同的getProduct可以實現/// 不同的產出產品,不同的產品不用再修改WrapProduct中的方法/// </summary>/// <param name="getProduct"></param>/// <returns></returns>public Box WrapProduct(Func<Product> getProduct, Action<Product> logCallback){Box box = new Box(); //準備一個BoxProduct product = getProduct.Invoke(); //獲取一個產品if (product.Price >= 50) //產品價格大于等于50則打印log信息{logCallback(product);}box.Product = product; //把產品裝到Box里面return box; //返回Box}}class ProductFactory{public Product MakePizza(){Product product = new Product();product.Name = "Pizza";product.Price = 12;return product;}public Product MakeToyCar(){Product product = new Product();product.Name = "Toy Car";product.Price = 100;return product;}} }回調關系是對于某個方法可以調用或者不調用,用的著的時候調用它,用不著得時候不調用它。回調方法還給了我們一個機會,可以動態的選擇后續將被調用的方法(有多個備選方法)。當以回調方法的形式使用委托時,需將委托類型的參數傳進主調方法里面,被傳入抓主調方法中的委托的參數它內部會封裝一個被回調的方法。主調函數會根據自己的邏輯來決定是否要調用回調方法。一般情況下,主調方法會在主要邏輯執行完之后,決定是否需要調用回調方法。
無論是模板方法還是回調方法,其本質都是用委托類型的參數封裝一個外部的方法,然后將這個委托傳入方法的內部來進行間接調用。委托的功能非常強大,但使用不當會造成嚴重的后果。
單播委托:一個委托封裝一個方法
多播委托:使用一個委托封裝多個方法,在調用時執行的順序按照封裝的順序
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading;namespace 委托_劉鐵猛 {class Program{static void Main(string[] args){Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.White };Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Blue };Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Yellow };Action action1 = new Action(stu1.DoHomework);Action action2 = new Action(stu2.DoHomework);Action action3 = new Action(stu3.DoHomework);//單播委托action1.Invoke();action2.Invoke();action3.Invoke();//多播委托action1 += action2;action1 += action3;action1.Invoke();}}class Student{public int ID { get; set; }public ConsoleColor PenColor { get; set; }public void DoHomework(){for (int i = 0; i < 5; i++){Console.ForegroundColor = this.PenColor;Console.WriteLine("Student {0} doing homework {1} hours", this.ID, i);Thread.Sleep(1000); //誰調用DoHomewrok方法誰就休眠1000mS}}} }委托還有另外一種高級使用方式,叫做隱式異步調用。
什么叫做隱式異步調用?我們可以將隱式異步調用分為2部分,即隱式、異步調用,接下來我們針對每一部分進行理解。
【隱式和顯式】
- 隱式指的是自動多線程,程序會自動創建分支線程輔助任務的執行。
- 顯式指的是用戶使用Thread或Task手動創建分支線程進行任務的執行。
【同步和異步】
異步調用與同步調用是相對的,同步和異步這兩個詞在中文和英文中的意思有些差別。
同步指的是兩個人做事情,你先做等你做完了我在你做的基礎之上接著做,我做完了你再在我做的基礎上接著做。
異步指的是兩個人做事情,你做你的,我做我的,我們各不相干同時在做。
每一個程序運行起來之后都是內存當中的一個進程(Process),每一個進程都可能包含一個或者多個線程(Thread)。當程序啟動的時候會生成一個進程,這個進程里面一定會有第一個運行起來的線程。這個第一個運行起來的線程就是這個進程或者說這個程序的主線程。進程中除主線程之外還可以有其它線程,主線程之外的線程叫做分支線程。
【同步調用和異步調用】
我們再來看一下方法的調用。當我們在同一個線程內去調用方法的時候,方法的執行是按照方法的調用先后順序執行的,即前一個執行完了后一個才能得到執行。這種在同一個線程內依次執行的方法調用,叫做同步調用。
?
上圖是典型的同步調用,圖中紅色部分表示主線程,其它顏色的表示在主線程中調用的不同方法。我們在主線程里調用了3個方法,主線程最先開始執行,然后調用第1個藍色表示的方法。當第1個方法執行的時候,CPU的執行指針就進入到第1個方法里,而主線程就暫停在這個地方。直到第1個方法執行完成,執行指針返回到的主線程,主線程才可以繼續執行。如此循環調用第2個、第3個方法,直到第3個方法執行完,執行指針再次返回到主線程,主線程執行完成后程序結束。
?
上圖是典型的異步調用,圖中紅色部分表示主線程,其它顏色的表示在主線程中調用的不同方法。異步調用指的就是在不同的線程當中去調用方法,每個線程和另外的線程互不相干。一個線程的開始和結束不會影響到另外一個線程的開始和結束,而且不同的開始與結束時機又構成不同的運行組合。這就是我們對方法的異步調用,也叫做多線程調用。換句話來說,異步調用它的底層機理就是多線程。
?
【同步調用的三種方法】
同步調用指的就是在單線程里進行串行的調用,同步調用有三種形式:
- 第一種是直接同步調用。直接同步調用就是用方法的名字來調用這個方法。
- 第二種是單播委托間接同步調用。使用單播委托按順序對方法進行間接的調用,使用Invoke的調用是同步調用,盡管是間接調用,但程序運行起來也是同步的。千萬不要把直接、間接與同步、異步搞混了,這兩組概念是相互獨立的。
- 第三種是多播委托間接同步調用。使用多播委托是將多個方法按順序綁定到同一個委托上,使用Invoke的調用是同步調用,通過對這一個委托的調用實現對委托上綁定的方法按照其綁定的先后順序順序由前向后依次執行。
上面代碼的執行結果如下:
【異步調用的三種方法】
異步調用的指的就是使用多線程進行并行的調用,異步調用有三種形式:
- 第一種是使用委托進行隱式異步調用。這是委托的另一種高級用法,使用委托的BeginInvoke方法的調用是異步調用。使用BeginInvoke時會自動生成一個分支線程,在這個分支線程里面調用委托中封裝的方法。使用BeginInvoke調用委托中的方法,會發現主線程以及三個分支線程在調用后就開始并行的執行,誰都不會等著誰執行完再執行,這就是典型的異步調用。三個線程同時訪問控制臺的前景色屬性,多個線程在訪問同一個資源的時候就可能在爭搶這個資源的時候發生沖突,現在我們看到的就是發生沖突的場景。為了避免這個沖突,有的時候我們會為線程加鎖避免多線程程序當中的資源沖突。
- 第二種是使用Thread的顯式異步調用。使用比較古老的Thread手動聲明多線程。
- 第三種是使用Task的顯式異步調用。使用更高級的Task手動聲明多線程。
使用接口取代委托:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace 委托_劉鐵猛 {public delegate double Calc(double x, double y); //定義委托class Program{static void Main(string[] args){IProductFactory pizzaFactory = new PizzaFactory();ToyCarFactory toyFactory = new ToyCarFactory();WrapFactory wrapFactory = new WrapFactory();Box box1 = wrapFactory.WrapProduct(pizzaFactory);Box box2 = wrapFactory.WrapProduct(toyFactory);Console.WriteLine(box1.Product.Name);Console.WriteLine(box2.Product.Name);}}interface IProductFactory{Product Make();}class PizzaFactory:IProductFactory{public Product Make(){Product product = new Product();product.Name = "Pizza";return product;}}class ToyCarFactory:IProductFactory{public Product Make(){Product product = new Product();product.Name = "Toy Car";return product;}}class Product{public string Name { get; set; }}class Box{public Product Product { get; set; }}class WrapFactory{public Box WrapProduct(IProductFactory productFactory){Box box = new Box(); //準備一個BoxProduct product = productFactory.Make(); //獲取一個產品box.Product = product; //把產品裝到Box里面return box; //返回Box}} }?
?
總結
以上是生活随笔為你收集整理的C#语言入门详解---委托(刘铁猛)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: for循环延时_单片机的独立按键学习,实
- 下一篇: C#中的前台线程和后台线程的区别