C#秘密武器之委托
在C#的世界里,委托是無處不在,尤其在.NET自己封裝的框架里到處都有其身影,所以掌握委托就很有必要了!那什么是委托呢?其實(shí)委托就是一種數(shù)據(jù)類型,跟int等東東差不多,只不過在使用時(shí)要自己先去構(gòu)建一個(gè)委托數(shù)據(jù)類型(不像int微軟已為你準(zhǔn)備好),然后聲明一個(gè)委托類型的變量,并為其賦值,賦值的對象只能是方法,最后通過委托變量就可以進(jìn)行方法調(diào)用了!
委托的簡單使用
如下定義了一個(gè)委托類型 - Calculator:
delegate int Calculator (int x);此委托適用于任何有著int返回類型和一個(gè)int類型參數(shù)的方法,如:
static int Double (int x) { return x * 2; }創(chuàng)建一個(gè)委托實(shí)例,將該此方法賦值給該委托實(shí)例:
Calculator c = new Calculator(Double);也可以簡寫成:
Calculator c = Double;這個(gè)方法可以通過委托調(diào)用:
int result = c(2);下面是完整代碼:
delegate int Calculator(int x);class Program {static int Double(int x) { return x * 2; }static void Main(string[] args) {Calculator c = Double;int result = c(2);Console.Write(result);Console.ReadKey();} }一、多路委托
委托類實(shí)際繼承于MulticastDelegate,這使委托對象支持多路委托,即委托對象可以綁定多個(gè)方法。當(dāng)輸入?yún)?shù)后,每個(gè)方法會“按順序”執(zhí)行!這里不建議委托方法有返回值,即使有返回值,多路委托中的方法也只能返回最后一個(gè)方法的返回值!
例如:
MyDelegate d = MyMethod1; // “+=” 用來添加,同理“-=”用來移除。 d += MyMethod2; // d -= MyMethod2調(diào)用時(shí),按照方法被添加的順序依次執(zhí)行。注意,對于委托,+= (實(shí)質(zhì)是調(diào)用的Delegate的Combine方法)和 -= (實(shí)質(zhì)是調(diào)用的Delegate的Remove方法)
MyDelegate d; d += MyMethod1;// 相當(dāng)于MyDelegate d = MyMethod1;我們先定義一個(gè)委托(ProgressReporter),然后定義一個(gè)匹配方法(Match)來執(zhí)行該委托中的所有方法。如下:
public delegate void ProgressReporter(int percentComplete);public class Utility {public static void Match(ProgressReporter p) {if (p != null) {for (int i = 0; i <= 10; i++) {p(i * 10);System.Threading.Thread.Sleep(100);}}} }然后我們需要兩個(gè)監(jiān)視進(jìn)度的方法,一個(gè)把進(jìn)度寫到Console,另一個(gè)把進(jìn)度寫到文件。如下:
class Program {static void Main(string[] args) {ProgressReporter p = WriteProgressToConsole;p += WriteProgressToFile;Utility.Match(p);Console.WriteLine("Done.");Console.ReadKey();}static void WriteProgressToConsole(int percentComplete) {Console.WriteLine(percentComplete+"%");}static void WriteProgressToFile(int percentComplete) {System.IO.File.AppendAllText("progress.txt", percentComplete + "%");} }運(yùn)行結(jié)果:
注意:同一類型的委托變量可以相加減!
二、委托體現(xiàn)的插件式編程思想
其實(shí)就是把委托(方法)作為一種方法的參數(shù)來進(jìn)行傳遞,由于同一委托可以接收多種不同實(shí)現(xiàn)的方法(插件),從而實(shí)現(xiàn)了一種插件式編程,實(shí)現(xiàn)動態(tài)的擴(kuò)展。
例如,我們有一個(gè)Utility類,這個(gè)類實(shí)現(xiàn)一個(gè)通用方法(Calculate),用來執(zhí)行任何有一個(gè)整型參數(shù)和整型返回值的方法。這樣說有點(diǎn)抽象,下面來看一個(gè)例子:
delegate int Calculator(int x);class Program {static int Double(int x) { return x * 2; }static void Main(string[] args) {int[] values = { 1,2,3,4};Utility.Calculate(values, Double);foreach (int i in values)Console.Write(i + " "); // 2 4 6 8Console.ReadKey();} }class Utility {public static void Calculate(int[] values, Calculator c) {for (int i = 0; i < values.Length; i++)values[i] = c(values[i]);} }這個(gè)例子中的Utility是固定不變的,程序?qū)崿F(xiàn)了整數(shù)的Double功能。我們可以把這個(gè)Double方法看作是一個(gè)插件,如果將來還要實(shí)現(xiàn)諸如求平方、求立方的計(jì)算,我們只需向程序中不斷添加插件就可以了。
如果Double方法是臨時(shí)的,只調(diào)用一次,若在整個(gè)程序中不會有第二次調(diào)用,那么我們可以在Main方法中更簡潔更靈活的使用這種插件式編程,無需先定義方法,使用λ表達(dá)式即可,如:
... Utility.Calculate(values, x => x * 2); ...三、靜態(tài)方法和實(shí)例方法對于委托的區(qū)別
當(dāng)一個(gè)類的實(shí)例的方法被賦給一個(gè)委托對象時(shí),在上下文中不僅要維護(hù)這個(gè)方法,還要維護(hù)這個(gè)方法所在的實(shí)例。System.Delegate 類的Target屬性指向的就是這個(gè)實(shí)例。舉個(gè)例子:
class Program {static void Main(string[] args) {X x = new X();ProgressReporter p = x.InstanceProgress;p(1);Console.WriteLine(p.Target == x); // TrueConsole.WriteLine(p.Method); // Void InstanceProgress(Int32)}static void WriteProgressToConsole(int percentComplete) {Console.WriteLine(percentComplete+"%");}static void WriteProgressToFile(int percentComplete) {System.IO.File.AppendAllText("progress.txt", percentComplete + "%");} }class X {public void InstanceProgress(int percentComplete) {// do something} }但對于靜態(tài)方法,System.Delegate 類的Target屬性是Null,所以將靜態(tài)方法賦值給委托時(shí)性能更優(yōu)。
四、泛型委托
如果你知道泛型,那么就很容易理解泛型委托,說白了就是含有泛型參數(shù)的委托,例如:
public delegate T Calculator<T> (T arg);我們可以把前面的例子改成泛型的例子,如下:
public delegate T Calculator<T>(T arg);class Program {static int Double(int x) { return x * 2; }static void Main(string[] args) {int[] values = { 1, 2, 3, 4 };Utility.Calculate(values, Double);foreach (int i in values)Console.Write(i + " "); // 2 4 6 8Console.ReadKey();} }class Utility {public static void Calculate<T>(T[] values, Calculator<T> c) {for (int i = 0; i < values.Length; i++)values[i] = c(values[i]);} }Func委托
委托 Func 支持 0~16 個(gè)參數(shù),Func 必須具有返回值
public delegate TResult Func<TResult>()
public delegate TResult Func<T1,TResult>(T1 obj1)
public delegate TResult Func<T1,T2,TResult>(T1 obj1,T2 obj2)
public delegate TResult Func<T1,T2,T3,TResult>(T1 obj1,T2 obj2,T3 obj3)
............
public delegate TResult Func<T1,T2,T3,......,T16,TResult>(T1 obj1,T2 obj2,T3 obj3,......,T16 obj16)
Action委托
Action<T> 的返回值為 void,也就是沒有返回值!
Action 支持0~16個(gè)參數(shù),可以按需求任意使用。
public delegate void Action()
public delegate void Action<T1>(T1 obj1)
public delegate void Action<T1,T2> (T1 obj1, T2 obj2)
public delegate void Action<T1,T2,T3> (T1 obj1, T2 obj2,T3 obj3)
............
public delegate void Action<T1,T2,T3,......,T16> (T1 obj1, T2 obj2,T3 obj3,......,T16 obj16)
Predicate<T>委托
Predicate只有一個(gè)參數(shù),且返回值總是為bool類型!
public delegate bool Predicate<T>(T obj)
internal class PredicateDelegate{public bool PredicateMethod(int x ){return x > 50;}}PredicateDelegate predicateDelegate = new PredicateDelegate();// 只有一個(gè)參數(shù) 并返回bool 值Predicate<int> predicate = predicateDelegate.PredicateMethod;bool results =predicate(60);Console.WriteLine(results);Comparison委托
public delegate int Comparison<in T>(T x, T y)
為返回int類型的內(nèi)置委托。T是要比較的對象的類型,而返回值是一個(gè)有符號整數(shù),指示?x 與?y 的相對值,如下表所示:
| 小于 0 | x 小于?y。 |
| 0 | x 等于?y。 |
| 大于 0 | x 大于?y。 |
此委托由?Array 類的 Sort<T>(T[], Comparison<T>)?方法重載和?List<T>?類的?Sort(Comparison<T>)?方法重載使用,用于對數(shù)組或列表中的元素進(jìn)行排序。
五、匿名方法和Lambda表達(dá)式
匿名方法
如果某個(gè)委托變量需要綁定的方法只用一次的話,其實(shí)是沒有必要為方法起名的,直接用匿名方法會比較好!我們總是使用 delegate(){......} 的方式建立匿名方法!
using System; using System.Collections.Generic; using System.Linq; using System.Text;namespace DelegateSamples {//聲明一個(gè)委托,參數(shù)為string,無返回值delegate void DelSamples(string msg);class Program{static void Main(string[] args){//匿名委托DelSamples delSample4 = delegate(string msg){Console.WriteLine("你好,我是{0}", msg);};delSample4("KoalaStudio");//利用Lambda表達(dá)式實(shí)現(xiàn)匿名委托DelSamples delSample5 = (string msg) => {Console.WriteLine("你好,我是{0}", msg);};delSample5("KoalaStudio");Console.ReadKey();}} }Lambda表達(dá)式
匿名方法的優(yōu)雅寫法就是Lambda表達(dá)式!
注意事項(xiàng):
①Lambda表達(dá)式中的參數(shù)列表(參數(shù)數(shù)量、類型和位置)必須與委托相匹配;
②表達(dá)式中的參數(shù)列表不一定需要包含類型,除非委托有ref或out關(guān)鍵字(此時(shí)必須顯示聲明);
③如果沒有參數(shù),必須使用一組空的圓括號;
static void Main(string[] args){Action<int> action = (x) =>{x = x + 500;Console.WriteLine("Result is : " + x);};action.Invoke(1000);Console.ReadKey();}六、異步委托
public delegate int Ad(int x,int y);static void Main(string[] args){xu xus = new xu();Ad a = new Ad(Add);Console.WriteLine(a(3, 3));Console.WriteLine("start");Console.ReadLine();} static int Add(int x, int y){Thread.Sleep(2000);return x + y;}運(yùn)行這段代碼 會先停頓2秒鐘之后再顯示6 和start 因?yàn)槲沂褂昧藄leep這個(gè)方法 它使該線程休眠2秒鐘,所以會在2秒之后顯示信息,但是這對用戶體驗(yàn)來說是非常糟糕的,那我們怎么改善呢?看看如下代碼
public delegate int Ad(int x,int y);static void Main(string[] args){xu xus = new xu();Ad a = new Ad(Add);Console.WriteLine(a(3, 3));// Console.WriteLine("start");IAsyncResult isa= a.BeginInvoke(3, 3, null, null);while (!isa.IsCompleted){ Console.WriteLine("未完成");}int s= a.EndInvoke(isa);Console.WriteLine(s.ToString());Console.ReadLine();}static int Add(int x, int y){Thread.Sleep(2000);return x + y;}static int ex(int x, int y){//Thread.Sleep(5000);return x - y;}這里我們使用了begininvoke方法來異步執(zhí)行 委托方法返回一個(gè)IAsyncResult 類型的值 代表委托執(zhí)行的狀態(tài),使用一個(gè)while循環(huán) 來判斷IsCompleted 如果沒有完成異步調(diào)用則不斷顯示“未完成” 如果完成endinvoke 則返回結(jié)果。但是這里需要不斷的詢問操作完成狀態(tài) 那么我們怎樣讓委托異步調(diào)用完成之后主動通知我們呢? 看看如下代碼
public delegate int Ad(int x,int y);static void Main(string[] args){xu xus = new xu();Ad a = new Ad(Add);Console.WriteLine(a(3, 3));IAsyncResult isa= a.BeginInvoke(3, 3, new AsyncCallback(call), "edit by xyl");//執(zhí)行你想執(zhí)行的代碼 這里我們還是用IsCompleted來代替while (!isa.IsCompleted){ Console.WriteLine("未完成");}Console.ReadLine();}static void call(IAsyncResult isa){AsyncResult ar = (AsyncResult)isa;Ad a = (Ad)ar.AsyncDelegate;Console.WriteLine("this is {0},{1}",a.EndInvoke(isa),ar.AsyncState);}static int Add(int x, int y){Thread.Sleep(2000);return x + y;}static int ex(int x, int y){//Thread.Sleep(5000);return x - y;}}這里我們使用了一個(gè)call方法 注意它是沒有返回值的。把IAsyncResult轉(zhuǎn)換成AsyncResult注意少了個(gè)I然后轉(zhuǎn)換成AD 類型的委托 最后endinvoke 來返回值 這樣在委托異步執(zhí)行完成之后會自動通知方法。
注意:回調(diào)函數(shù)是在 ThreadPool線程上進(jìn)行的,因此主線程將繼續(xù)執(zhí)行。ThreadPool線程是后臺線程,這些線程不會在主線程結(jié)束后保持應(yīng)用程序的運(yùn)行,因此主線程必須休眠足夠長的時(shí)間以便回調(diào)完成。我們也可以在主線程完成操作后調(diào)用IsCompleted屬性判斷委托函數(shù)是否完成。
七、事件
事件其實(shí)就是某種類型的委托,在給事件賦值時(shí),用符合該委托的方法就行!
public delegate void PriceChangedHandler (decimal oldPrice, decimal newPrice); public class IPhone6 {public event PriceChangedHandler PriceChanged; }事件的使用和委托完全一樣,只是多了些約束。下面是一個(gè)簡單的事件使用例子:
public delegate void PriceChangedHandler(decimal oldPrice, decimal newPrice);public class IPhone6 {decimal price;public event PriceChangedHandler PriceChanged;public decimal Price {get { return price; }set {if (price == value) return;decimal oldPrice = price;price = value;// 如果調(diào)用列表不為空,則觸發(fā)。if (PriceChanged != null)PriceChanged(oldPrice, price);}} }class Program {static void Main() {IPhone6 iphone6 = new IPhone6() { Price = 5288 };// 訂閱事件iphone6.PriceChanged += iphone6_PriceChanged;// 調(diào)整價(jià)格(事件發(fā)生)iphone6.Price = 3999;Console.ReadKey();}static void iphone6_PriceChanged(decimal oldPrice, decimal price) {Console.WriteLine("年終大促銷,iPhone 6 只賣 " + price + " 元, 原價(jià) " + oldPrice + " 元,快來搶!");} }運(yùn)行結(jié)果:
有人可能會問,如果把上面的event關(guān)鍵字拿掉,結(jié)果不是一樣的嗎,到底有何不同?
沒錯可以用事件的地方就一定可以用委托代替。
但事件有一系列規(guī)則和約束用以保證程序的安全可控,事件只有 += 和 -= 操作,這樣訂閱者只能有訂閱或取消訂閱操作,沒有權(quán)限執(zhí)行其它操作。如果是委托,那么訂閱者就可以使用 = 來對委托對象重新賦值(其它訂閱者全部被取消訂閱),甚至將其設(shè)置為null,甚至訂閱者還可以直接調(diào)用委托,這些都是很危險(xiǎn)的操作,廣播者就失去了獨(dú)享控制權(quán)。
事件保證了程序的安全性和健壯性。
事件的標(biāo)準(zhǔn)模式
.NET 框架為事件編程定義了一個(gè)標(biāo)準(zhǔn)模式。設(shè)定這個(gè)標(biāo)準(zhǔn)是為了讓.NET框架和用戶代碼保持一致。System.EventArgs是標(biāo)準(zhǔn)模式的核心,它是一個(gè)沒有任何成員,用于傳遞事件參數(shù)的基類。
按照標(biāo)準(zhǔn)模式,我們對于上面的iPhone6示例進(jìn)行重寫。首先定義EventArgs:
然后為事件定義委托,必須滿足以下條件:
- 必須是 void 返回類型;
- 必須有兩個(gè)參數(shù),且第一個(gè)是object類型,第二個(gè)是EventArgs類型(的子類);
- 它的名稱必須以EventHandler結(jié)尾。
由于考慮到每個(gè)事件都要定義自己的委托很麻煩,.NET 框架為我們預(yù)定義好一個(gè)通用委托System.EventHandler<TEventArgs>:
public delegate void EventHandler<TEventArgs> (object source, TEventArgs e) where TEventArgs : EventArgs;如果不使用框架的EventHandler<TEventArgs>,我們需要自己定義一個(gè):
public delegate void PriceChangedEventHandler (object sender, PriceChangedEventArgs e);如果不需要參數(shù),可以直接使用EventHandler(不需要<TEventArgs>)。有了EventHandler<TEventArgs>,我們就可以這樣定義示例中的事件:
public class IPhone6 {...public event EventHandler<PriceChangedEventArgs> PriceChanged;... }最后,事件標(biāo)準(zhǔn)模式還需要寫一個(gè)受保護(hù)的虛方法來觸發(fā)事件,這個(gè)方法必須以O(shè)n為前綴,加上事件名(PriceChanged),還要接受一個(gè)EventArgs參數(shù),如下:
public class IPhone6 {...public event EventHandler<PriceChangedEventArgs> PriceChanged;protected virtual void OnPriceChanged(PriceChangedEventArgs e) {if (PriceChanged != null) PriceChanged(this, e);}... }下面給出完整示例:
public class PriceChangedEventArgs : System.EventArgs {public readonly decimal OldPrice;public readonly decimal NewPrice;public PriceChangedEventArgs(decimal oldPrice, decimal newPrice) {OldPrice = oldPrice;NewPrice = newPrice;} }public class IPhone6 {decimal price;public event EventHandler<PriceChangedEventArgs> PriceChanged;protected virtual void OnPriceChanged(PriceChangedEventArgs e) {if (PriceChanged != null) PriceChanged(this, e);}public decimal Price {get { return price; }set {if (price == value) return;decimal oldPrice = price;price = value;// 如果調(diào)用列表不為空,則觸發(fā)。if (PriceChanged != null)OnPriceChanged(new PriceChangedEventArgs(oldPrice, price));}} }class Program {static void Main() {IPhone6 iphone6 = new IPhone6() { Price = 5288M };// 訂閱事件iphone6.PriceChanged +=iphone6_PriceChanged;// 調(diào)整價(jià)格(事件發(fā)生)iphone6.Price = 3999;Console.ReadKey();}static void iphone6_PriceChanged(object sender, PriceChangedEventArgs e) {Console.WriteLine("年終大促銷,iPhone 6 只賣 " + e.NewPrice + " 元, 原價(jià) " + e.OldPrice + " 元,快來搶!");} }運(yùn)行結(jié)果:
?
轉(zhuǎn)載于:https://www.cnblogs.com/WeiGe/p/4217600.html
總結(jié)
- 上一篇: 炫酷的进度条的效果
- 下一篇: 解决外部引用的js文件不能获取服务端组件