程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#委托和事件本質

C#委托和事件本質

編輯:C#入門知識

C#委托和事件本質


C#中委托和事件是很重要的組成部分,而掌握委托和事件的本質將必不可少。為了能探秘本質,寫了如下代碼   復制代碼 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks;   namespace ConsoleApplication1 {     class Program     {         static void Main(string[] args)         {             Water water = new Water();               water.WaterBoils += water_WaterBoils;             water.WaterBoils += water_WaterBoils2;               water.DegreeRise();             Console.ReadLine();         }           static void water_WaterBoils(object sender, WaterBoilsEventArgs args)         {             Console.WriteLine(string.Format("sender:{0}", sender.ToString()));             Console.WriteLine(string.Format("args:{0}", args.CurentDegree.ToString()));         }         public static void water_WaterBoils2(object sender, WaterBoilsEventArgs args)         {             Console.WriteLine(string.Format("sender_2:{0}", sender.ToString()));             Console.WriteLine(string.Format("args_2:{0}", args.CurentDegree.ToString()));         }     }       public delegate void WaterBoilsEventHandler(object sender,WaterBoilsEventArgs args);       public class WaterBoilsEventArgs : EventArgs     {         public int CurentDegree { get; set; }     }       public class Water     {         public event WaterBoilsEventHandler WaterBoils;         public int curentDegree = 0;           public void DegreeRise()         {             for(var n = 99;n<200;n++)             {                 Thread.Sleep(1000);                   if(n>=100)                 {                     if(WaterBoils!=null)                     {                         WaterBoils(this,new WaterBoilsEventArgs(){CurentDegree = n});                           //WaterBoils.Invoke(this, new WaterBoilsEventArgs() { CurentDegree = n });                     }                 }             }         }     } } 復制代碼           介紹一下這段代碼:定義了一個委托WaterBoilsEventHandler,一個類Water,Water的DegreeRise方法觸發WaterBoils事件,一個事件參數WaterBoilsEventArgs ,一個Main方法.             既然是要看委托了事件的本質,所以借助反編譯工具(Reflector)查看編譯後的代碼片段:   首先來看一下委托編譯以後的代碼:   復制代碼 .class public auto ansi sealed WaterBoilsEventHandler     extends [mscorlib]System.MulticastDelegate {     .method public hidebysig specialname rtspecialname instance void .ctor(object 'object', native int 'method') runtime managed     {     }       .method public hidebysig newslot virtual instance class [mscorlib]System.IAsyncResult BeginInvoke(object sender,  class ConsoleApplication1.WaterBoilsEventArgs args, class [mscorlib]System.AsyncCallback callback, object 'object') runtime managed     {     }       .method public hidebysig newslot virtual instance void EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed     {     }       .method public hidebysig newslot virtual instance void Invoke(object sender, class ConsoleApplication1.WaterBoilsEventArgs args)  runtime managed     {     }   } 復制代碼 從反編譯IL代碼中可以看出:            1.自定義委托的本質是一個類,具有Invoke,BeginInvoke,Endinvoke方法分別來支持同步和異步的執行,Invoke無返回值;BeginInvoke返回IAsyncResult,IAsyncResult可以作為EndInvoke的參數來結束執行。            2.自定義委托繼承自MulticastDelegate,使自定義委托具有“組播”的能力,也就是可以綁定多個委托;(MulticastDelegate類又繼承自Delegate)   下面來看看“組播”是怎麼實現的:MulticastDelegate內有兩個方法CombineImpl和RemoveImpl分別完成委托的綁定和解綁定;內部有一個對象(使用中會轉換成object[])_invocationList存儲MulticastDelegate對象;還有其他的一些方法共同完成。       從上邊可以看出,委托是可以綁定執行多個方法的,那為什麼還要事件呢,我覺得這個問題可能從語言設計角度上講,委托的設計出發點是規避猶如在C、C++等中的指針變量,委托具把這個地址進行了包裝,反編譯Delegate類,發現有如下成員   QQ截圖20140712133307   _methodPtr:方法的指針,一個Delegate對象維護了一個方法的引用地址   在使用委托的是否發現有有悖的地方:《 C# 與 .Net 3.5 高級程序設計第四版》有如下解釋:   “如果我們沒有把委托成員變量定義為私有的,調用者就可以直接訪問委托對象,這樣調用者就可以把變量賦值為新的委托對象(實際上也就是刪除了當前要調用的方法列表),更糟糕的是,調用者可以直接調用委托的調用列表。”   這裡就有一個比較嚴重的問題:1.比如兩個對象去注冊,A已經注冊了一個方法,B去注冊的時候把A注冊的方法給刪除了,並且B還可以操作到A注冊的方法。這種不友好是不能忍受的。   C#提供了event關鍵字來為我們解決問題,使用過event的童鞋們都可能有印象,在聲明事件以外的其他類中,如果調用事件,只能干兩件事情:     1.綁定方法引用,     2.解綁方法引用。   這是對事件的限制。   順著這條思路,看看C#中事件是怎麼完成的。   首先看一看,Water類的反編譯結果   復制代碼 .class public auto ansi beforefieldinit Water     extends [mscorlib]System.Object {     .event ConsoleApplication1.WaterBoilsEventHandler WaterBoils     {         .addon instance void ConsoleApplication1.Water::add_WaterBoils(class ConsoleApplication1.WaterBoilsEventHandler)         .removeon instance void ConsoleApplication1.Water::remove_WaterBoils(class ConsoleApplication1.WaterBoilsEventHandler)     }         .method public hidebysig specialname rtspecialname instance void .ctor() cil managed     {     }       .method public hidebysig instance void DegreeRise() cil managed     {     }         .field public int32 curentDegree       .field private class ConsoleApplication1.WaterBoilsEventHandler WaterBoils   } 復制代碼 add_WaterBoils方法的方法定義如下:   復制代碼 public void add_WaterBoils(WaterBoilsEventHandler value) {     WaterBoilsEventHandler handler2;     WaterBoilsEventHandler waterBoils = this.WaterBoils;     do     {         handler2 = waterBoils;         WaterBoilsEventHandler handler3 = (WaterBoilsEventHandler) Delegate.Combine(handler2, value);         waterBoils = Interlocked.CompareExchange<WaterBoilsEventHandler>(ref this.WaterBoils, handler3, handler2);     }     while (waterBoils != handler2); } 復制代碼 remove_WaterBoils方法的方法定義如下   復制代碼 public void remove_WaterBoils(WaterBoilsEventHandler value) {     WaterBoilsEventHandler handler2;     WaterBoilsEventHandler waterBoils = this.WaterBoils;     do     {         handler2 = waterBoils;         WaterBoilsEventHandler handler3 = (WaterBoilsEventHandler) Delegate.Remove(handler2, value);         waterBoils = Interlocked.CompareExchange<WaterBoilsEventHandler>(ref this.WaterBoils, handler3, handler2);     }     while (waterBoils != handler2); } 復制代碼 可以看出:   1.增加了兩個方法:add_WaterBoils 和 remove_WaterBoils;   2.還增加了一個WaterBoilsEventHandler 類型的 私有變量 WaterBoils且與聲明的事件對象名相同   這裡容易產生一點迷惑:.event ConsoleApplication1.WaterBoilsEventHandler WaterBoils 有點像一個內部類的結構,且這個“類”具有兩個方法。暫時把這個迷惑放下。   3.add_WaterBoils和remove_WaterBoils方法都是對私有字段WaterBoils的維護。   知道這些後,再開看看注冊的時候是怎麼調用的:   復制代碼 private static void Main(string[] args) {     Water water = new Water();     water.WaterBoils += new WaterBoilsEventHandler(Program.water_WaterBoils);     water.WaterBoils += new WaterBoilsEventHandler(Program.water_WaterBoils2);     water.DegreeRise();     Console.ReadLine(); } 復制代碼 這是使用的C#代碼方式,看不出來什麼結果,查看IL代碼如下:   復制代碼 .method private hidebysig static void Main(string[] args) cil managed {     .entrypoint     .maxstack 3     .locals init (         [0] class ConsoleApplication1.Water water)     L_0000: nop      L_0001: newobj instance void ConsoleApplication1.Water::.ctor()     L_0006: stloc.0      L_0007: ldloc.0      L_0008: ldnull      L_0009: ldftn void ConsoleApplication1.Program::water_WaterBoils(object, class ConsoleApplication1.WaterBoilsEventArgs)     L_000f: newobj instance void ConsoleApplication1.WaterBoilsEventHandler::.ctor(object, native int)     L_0014: callvirt instance void ConsoleApplication1.Water::add_WaterBoils(class ConsoleApplication1.WaterBoilsEventHandler)     L_0019: nop      L_001a: ldloc.0      L_001b: ldnull      L_001c: ldftn void ConsoleApplication1.Program::water_WaterBoils2(object, class ConsoleApplication1.WaterBoilsEventArgs)     L_0022: newobj instance void ConsoleApplication1.WaterBoilsEventHandler::.ctor(object, native int)     L_0027: callvirt instance void ConsoleApplication1.Water::add_WaterBoils(class ConsoleApplication1.WaterBoilsEventHandler)     L_002c: nop      L_002d: ldloc.0      L_002e: callvirt instance void ConsoleApplication1.Water::DegreeRise()     L_0033: nop      L_0034: call string [mscorlib]System.Console::ReadLine()     L_0039: pop      L_003a: ret  } 復制代碼 重點分析一下 L_0009 到 L_0014 的這三行代碼:   L_0009 :把water_WaterBoils方法的指針推送到計算堆棧上   L_000f  :創建一個WaterBoilsEventHandler委托對象   L_0014 :調用add_WaterBoils,並把結果推送到計算堆棧上       同時請注意:“ConsoleApplication1.Water::add_WaterBoils”這句代碼,add_WaterBoils是ConsoleApplication1.Water類對象的方法,上邊的迷惑,看著像一個“內部類”,實則沒那關系。   這段代碼讓我們明白:事件的注冊是調用編譯生成的方法 add_WaterBoils 把 委托中帶有的方法引用地址 維護到編譯生成的私有屬性 WaterBoils 中去了。   所以event就是語法糖,節省了編寫代碼的時間,並且事件的觸發使用專門的事件來處理,顯出語言本身的完整,真正的實現是編譯器生成委托對象和其他處理代碼實現的。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved