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)查看編譯後的代碼片斷:ide

首先來看一下委託編譯之後的代碼:工具

.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代碼中能夠看出:this

         1.自定義委託的本質是一個類,具備Invoke,BeginInvoke,Endinvoke方法分別來支持同步和異步的執行,Invoke無返回值;BeginInvoke返回IAsyncResult,IAsyncResult能夠做爲EndInvoke的參數來結束執行。spa

         2.自定義委託繼承自MulticastDelegate,使自定義委託具備「組播」的能力,也就是能夠綁定多個委託;(MulticastDelegate類又繼承自Delegate設計

下面來看看「組播」是怎麼實現的:MulticastDelegate內有兩個方法CombineImpl和RemoveImpl分別完成委託的綁定和解綁定;內部有一個對象(使用中會轉換成object[])_invocationList存儲MulticastDelegate對象;還有其餘的一些方法共同完成。指針

 

從上邊能夠看出,委託是能夠綁定執行多個方法的,那爲何還要事件呢,我以爲這個問題可能從語言設計角度上講,委託的設計出發點是規避猶如在C、C++等中的指針變量,委託具把這個地址進行了包裝,反編譯Delegate類,發現有以下成員調試

QQ截圖20140712133307   _methodPtr:方法的指針,一個Delegate對象維護了一個方法的引用地址code

在使用委託的是否發現有有悖的地方:《 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就是語法糖,節省了編寫代碼的時間,而且事件的觸發使用專門的事件來處理,顯出語言自己的完整,真正的實現是編譯器生成委託對象和其餘處理代碼實現的

 

最近面試,遇到一個問題:「事件是否是一種委託?」

來看一下事件對象是什麼

在快速監視器中查看:

能夠看出,申明的事件對象( water.WaterBoils) 是委託(WaterBoilsEventHandler)實例。

「 .event ConsoleApplication1.WaterBoilsEventHandler WaterBoils」中的 「.event」  則表示 「WaterBoils」具備特殊性,是事件。

 

個人理解:

  C#中的事件就是一種語法糖,不是一些人認爲的是對委託的封裝,它是事件處理的一種指導性應用方式,event使其具備必定的約束性,帶有「最小化影響」的設計原則(不容許影響他人的註冊),它是依靠委託來實現的,但它不是一種委託,它是比委託更抽象的一種應用方式。

  而事件對象自己就是委託實例(CLR可能在運行的時候會把事件對象與相同名稱的私有field進行關聯,好比調試的時候能夠看出,事件對象的值就是該相同名稱的私有field的值)。

 

歡迎你們談談本身的見解 

 

但願與你們多多交流  QQ:373934650;羣Q:227231436

若是有用請大夥兒幫忙推薦一下,謝謝!

相關文章
相關標籤/搜索