C#语言入门详解---委托(刘铁猛)
委托:函數(shù)指針的升級(jí)版,可以類比C語言中的函數(shù)指針進(jìn)行理解
變量的本質(zhì)就是以變量名所對(duì)應(yīng)的內(nèi)存地址為起點(diǎn)的一段內(nèi)存,這段內(nèi)存中存儲(chǔ)的就是變量的數(shù)據(jù),這段內(nèi)存的大小由變量的數(shù)據(jù)類型決定。
函數(shù)代表算法,函數(shù)的本質(zhì)是以函數(shù)名所對(duì)應(yīng)的內(nèi)存地址為起點(diǎn)的一段內(nèi)存中,這段內(nèi)存中存儲(chǔ)的不是某個(gè)值,而是一組機(jī)器語言的指令,CPU就是按照這組指令一條一條執(zhí)行完成這段函數(shù)中所包含的算法。
無論是數(shù)據(jù)還是算法都是保存在內(nèi)存中的,變量是用來尋找數(shù)據(jù)的地址,函數(shù)是用來尋找算法的地址。
C#通過委托這種數(shù)據(jù)類型保留了與C/C++語言中函數(shù)指針對(duì)應(yīng)的功能。
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(); //直接調(diào)用action.Invoke(); //間接調(diào)用1action(); //間接調(diào)用2int a = 200;int b = 300;int c = 0;//直接調(diào)用c = caculator.Add(a, b);Console.WriteLine(c);c = caculator.Sub(a, b);Console.WriteLine(c);//間接調(diào)用1c = func1.Invoke(a, b);Console.WriteLine(c);c = func2.Invoke(a, b);Console.WriteLine(c);//間接調(diào)用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;}} }自定義委托時(shí)一定要注意:
1)委托與封裝的方法必須類型兼容,返回值和參數(shù)的數(shù)據(jù)類型和參數(shù)數(shù)目需一致
2)委托是一種類,因此將其聲明在名稱空間里面時(shí)其和其它類處于同一級(jí)。C#中允許嵌套類,即在一個(gè)類中可以嵌套另一個(gè)類,因此若將委托放到某一個(gè)類中,則委托就變成了該類中的嵌套類
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);//等價(jià)于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);//等價(jià)于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>/// 模板方法,委托的調(diào)用getProduct是可以修改的地方,傳入不同的getProduct可以實(shí)現(xiàn)/// 不同的產(chǎn)出產(chǎn)品,不同的產(chǎn)品不用再修改WrapProduct中的方法/// </summary>/// <param name="getProduct"></param>/// <returns></returns>public Box WrapProduct(Func<Product> getProduct){Box box = new Box(); //準(zhǔn)備一個(gè)BoxProduct product = getProduct.Invoke(); //獲取一個(gè)產(chǎn)品box.Product = product; //把產(chǎn)品裝到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;}} }參照如上代碼,體會(huì)使用模板方法的好處是:Product類、Box類、WrapFactory類都不用修改,只需要擴(kuò)展ProductFactory類中的產(chǎn)品就可以生產(chǎn)不同的產(chǎn)品。不管是生產(chǎn)哪種產(chǎn)品的方法,只要將該方法封裝到委托類型的對(duì)象里傳給模板方法,調(diào)用模板方法時(shí)就可以將產(chǎn)品包裝成箱子再交還回來,這樣可以最大限度的實(shí)現(xiàn)代碼的重復(fù)使用。代碼的復(fù)用不但可以提高工作效率,還可以減少程序Bug的引入,良好的復(fù)用結(jié)構(gòu)是所有優(yōu)秀軟件所追求的共同目標(biāo)之一。
回調(diào)方法:
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>/// 模板方法,委托的調(diào)用getProduct是可以修改的地方,傳入不同的getProduct可以實(shí)現(xiàn)/// 不同的產(chǎn)出產(chǎn)品,不同的產(chǎn)品不用再修改WrapProduct中的方法/// </summary>/// <param name="getProduct"></param>/// <returns></returns>public Box WrapProduct(Func<Product> getProduct, Action<Product> logCallback){Box box = new Box(); //準(zhǔn)備一個(gè)BoxProduct product = getProduct.Invoke(); //獲取一個(gè)產(chǎn)品if (product.Price >= 50) //產(chǎn)品價(jià)格大于等于50則打印log信息{logCallback(product);}box.Product = product; //把產(chǎn)品裝到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;}} }回調(diào)關(guān)系是對(duì)于某個(gè)方法可以調(diào)用或者不調(diào)用,用的著的時(shí)候調(diào)用它,用不著得時(shí)候不調(diào)用它?;卣{(diào)方法還給了我們一個(gè)機(jī)會(huì),可以動(dòng)態(tài)的選擇后續(xù)將被調(diào)用的方法(有多個(gè)備選方法)。當(dāng)以回調(diào)方法的形式使用委托時(shí),需將委托類型的參數(shù)傳進(jìn)主調(diào)方法里面,被傳入抓主調(diào)方法中的委托的參數(shù)它內(nèi)部會(huì)封裝一個(gè)被回調(diào)的方法。主調(diào)函數(shù)會(huì)根據(jù)自己的邏輯來決定是否要調(diào)用回調(diào)方法。一般情況下,主調(diào)方法會(huì)在主要邏輯執(zhí)行完之后,決定是否需要調(diào)用回調(diào)方法。
無論是模板方法還是回調(diào)方法,其本質(zhì)都是用委托類型的參數(shù)封裝一個(gè)外部的方法,然后將這個(gè)委托傳入方法的內(nèi)部來進(jìn)行間接調(diào)用。委托的功能非常強(qiáng)大,但使用不當(dāng)會(huì)造成嚴(yán)重的后果。
單播委托:一個(gè)委托封裝一個(gè)方法
多播委托:使用一個(gè)委托封裝多個(gè)方法,在調(diào)用時(shí)執(zhí)行的順序按照封裝的順序
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); //誰調(diào)用DoHomewrok方法誰就休眠1000mS}}} }委托還有另外一種高級(jí)使用方式,叫做隱式異步調(diào)用。
什么叫做隱式異步調(diào)用?我們可以將隱式異步調(diào)用分為2部分,即隱式、異步調(diào)用,接下來我們針對(duì)每一部分進(jìn)行理解。
【隱式和顯式】
- 隱式指的是自動(dòng)多線程,程序會(huì)自動(dòng)創(chuàng)建分支線程輔助任務(wù)的執(zhí)行。
- 顯式指的是用戶使用Thread或Task手動(dòng)創(chuàng)建分支線程進(jìn)行任務(wù)的執(zhí)行。
【同步和異步】
異步調(diào)用與同步調(diào)用是相對(duì)的,同步和異步這兩個(gè)詞在中文和英文中的意思有些差別。
同步指的是兩個(gè)人做事情,你先做等你做完了我在你做的基礎(chǔ)之上接著做,我做完了你再在我做的基礎(chǔ)上接著做。
異步指的是兩個(gè)人做事情,你做你的,我做我的,我們各不相干同時(shí)在做。
每一個(gè)程序運(yùn)行起來之后都是內(nèi)存當(dāng)中的一個(gè)進(jìn)程(Process),每一個(gè)進(jìn)程都可能包含一個(gè)或者多個(gè)線程(Thread)。當(dāng)程序啟動(dòng)的時(shí)候會(huì)生成一個(gè)進(jìn)程,這個(gè)進(jìn)程里面一定會(huì)有第一個(gè)運(yùn)行起來的線程。這個(gè)第一個(gè)運(yùn)行起來的線程就是這個(gè)進(jìn)程或者說這個(gè)程序的主線程。進(jìn)程中除主線程之外還可以有其它線程,主線程之外的線程叫做分支線程。
【同步調(diào)用和異步調(diào)用】
我們?cè)賮砜匆幌路椒ǖ恼{(diào)用。當(dāng)我們?cè)谕粋€(gè)線程內(nèi)去調(diào)用方法的時(shí)候,方法的執(zhí)行是按照方法的調(diào)用先后順序執(zhí)行的,即前一個(gè)執(zhí)行完了后一個(gè)才能得到執(zhí)行。這種在同一個(gè)線程內(nèi)依次執(zhí)行的方法調(diào)用,叫做同步調(diào)用。
?
上圖是典型的同步調(diào)用,圖中紅色部分表示主線程,其它顏色的表示在主線程中調(diào)用的不同方法。我們?cè)谥骶€程里調(diào)用了3個(gè)方法,主線程最先開始執(zhí)行,然后調(diào)用第1個(gè)藍(lán)色表示的方法。當(dāng)?shù)?個(gè)方法執(zhí)行的時(shí)候,CPU的執(zhí)行指針就進(jìn)入到第1個(gè)方法里,而主線程就暫停在這個(gè)地方。直到第1個(gè)方法執(zhí)行完成,執(zhí)行指針返回到的主線程,主線程才可以繼續(xù)執(zhí)行。如此循環(huán)調(diào)用第2個(gè)、第3個(gè)方法,直到第3個(gè)方法執(zhí)行完,執(zhí)行指針再次返回到主線程,主線程執(zhí)行完成后程序結(jié)束。
?
上圖是典型的異步調(diào)用,圖中紅色部分表示主線程,其它顏色的表示在主線程中調(diào)用的不同方法。異步調(diào)用指的就是在不同的線程當(dāng)中去調(diào)用方法,每個(gè)線程和另外的線程互不相干。一個(gè)線程的開始和結(jié)束不會(huì)影響到另外一個(gè)線程的開始和結(jié)束,而且不同的開始與結(jié)束時(shí)機(jī)又構(gòu)成不同的運(yùn)行組合。這就是我們對(duì)方法的異步調(diào)用,也叫做多線程調(diào)用。換句話來說,異步調(diào)用它的底層機(jī)理就是多線程。
?
【同步調(diào)用的三種方法】
同步調(diào)用指的就是在單線程里進(jìn)行串行的調(diào)用,同步調(diào)用有三種形式:
- 第一種是直接同步調(diào)用。直接同步調(diào)用就是用方法的名字來調(diào)用這個(gè)方法。
- 第二種是單播委托間接同步調(diào)用。使用單播委托按順序?qū)Ψ椒ㄟM(jìn)行間接的調(diào)用,使用Invoke的調(diào)用是同步調(diào)用,盡管是間接調(diào)用,但程序運(yùn)行起來也是同步的。千萬不要把直接、間接與同步、異步搞混了,這兩組概念是相互獨(dú)立的。
- 第三種是多播委托間接同步調(diào)用。使用多播委托是將多個(gè)方法按順序綁定到同一個(gè)委托上,使用Invoke的調(diào)用是同步調(diào)用,通過對(duì)這一個(gè)委托的調(diào)用實(shí)現(xiàn)對(duì)委托上綁定的方法按照其綁定的先后順序順序由前向后依次執(zhí)行。
上面代碼的執(zhí)行結(jié)果如下:
【異步調(diào)用的三種方法】
異步調(diào)用的指的就是使用多線程進(jìn)行并行的調(diào)用,異步調(diào)用有三種形式:
- 第一種是使用委托進(jìn)行隱式異步調(diào)用。這是委托的另一種高級(jí)用法,使用委托的BeginInvoke方法的調(diào)用是異步調(diào)用。使用BeginInvoke時(shí)會(huì)自動(dòng)生成一個(gè)分支線程,在這個(gè)分支線程里面調(diào)用委托中封裝的方法。使用BeginInvoke調(diào)用委托中的方法,會(huì)發(fā)現(xiàn)主線程以及三個(gè)分支線程在調(diào)用后就開始并行的執(zhí)行,誰都不會(huì)等著誰執(zhí)行完再執(zhí)行,這就是典型的異步調(diào)用。三個(gè)線程同時(shí)訪問控制臺(tái)的前景色屬性,多個(gè)線程在訪問同一個(gè)資源的時(shí)候就可能在爭搶這個(gè)資源的時(shí)候發(fā)生沖突,現(xiàn)在我們看到的就是發(fā)生沖突的場景。為了避免這個(gè)沖突,有的時(shí)候我們會(huì)為線程加鎖避免多線程程序當(dāng)中的資源沖突。
- 第二種是使用Thread的顯式異步調(diào)用。使用比較古老的Thread手動(dòng)聲明多線程。
- 第三種是使用Task的顯式異步調(diào)用。使用更高級(jí)的Task手動(dòng)聲明多線程。
使用接口取代委托:
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(); //準(zhǔn)備一個(gè)BoxProduct product = productFactory.Make(); //獲取一個(gè)產(chǎn)品box.Product = product; //把產(chǎn)品裝到Box里面return box; //返回Box}} }?
?
總結(jié)
以上是生活随笔為你收集整理的C#语言入门详解---委托(刘铁猛)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: for循环延时_单片机的独立按键学习,实
- 下一篇: C#中的前台线程和后台线程的区别