詳解C#委託和事件(一)

Minotauros發表於2018-09-14

  

  委託(Delegate)是安全封裝方法的型別,類似於C和C++中的函式指標,與函式指標不同的是,委託是物件導向的、型別安全的和可靠的;


  一、委託型別是CTS中五種基礎型別之一,是一種引用型別,表示對具有指定引數列表和返回型別的方法的引用,也是一種特殊的類型別,其型別為System.MulticastDelegate,繼承自抽象類System.Delegate;使用委託型別宣告的物件,即委託例項,是一種特殊型別的物件,其可以與任何該委託所能封裝的方法相關聯,然後通過委託例項呼叫這些方法;

※在名稱空間下宣告的委託型別與類型別相似,只能為public或internal,在類內部宣告的委託型別為當前類的巢狀型別,可以指定各種訪問修飾符;在類內部宣告的委託例項是一個欄位,在方法內部宣告的委託例項是一個區域性變數;

  1.委託的宣告與類的宣告方式相似,委託的型別由宣告委託的名稱確定:

    //宣告一個自定義的委託型別MyDelegate,可以封裝無引數列表,無返回值的方法
    public delegate void MyDelegate();

  2.每個委託型別都描述其所能封裝的方法的引數列表(引數數目和型別)以及返回值型別,每當需要封裝一組新的引數列表或返回值型別的方法時,都必須宣告一個新的委託型別;
  委託例項的宣告與物件的宣告方式相似,由委託型別和委託例項的名稱組成:

    //宣告一個MyDelegate型別的委託例項myDelegate
    private MyDelegate myDelegate;

  3.宣告和例項化委託時,可以使用new關鍵詞、直接用方法名賦值、匿名方法或者Lambda表示式;

  4.委託的一個重要特性是可以使用+或+=運算子將多個具有相同引數列表和返回值型別的方法或相同型別的委託例項新增到一個委託例項中,使用-或-=運算子可以從委託例項中移除指定的方法或委託例項,呼叫該委託例項時,會呼叫其中的所有方法,這個特性被稱為多播委託(Multicast Delegates),廣泛應用於事件中;
多播委託內包含已賦值委託的列表,在呼叫多播委託時,它會按順序呼叫列表中的委託;對於有返回值的多播委託,其返回值是按順序執行列表後最後一個方法的返回值;

public delegate void MyDelegate();
    public class MyClass
    {
        public void MyFunc()
        {
            Console.WriteLine("MyFunc Run");
        }
    }
    public class Program
    {
        static void Main(string[] args)
        {
            MyClass myClass = new MyClass();
            MyDelegate myDelegate;
            //1.使用new建立委託物件時指定方法
            myDelegate = new MyDelegate(myClass.MyFunc);  //先用=運算子建立例項,=後面同樣可以使用以下任何一種方式進行賦值
            //2.直接用方法名賦值
            myDelegate += myClass.MyFunc;
            //3.使用匿名方法賦值
            myDelegate += delegate ()
            {
                Console.WriteLine("Anonymous methods run");
            };
            //4.使用Lambda表示式賦值
            myDelegate += () =>
            {
                Console.WriteLine("Lambda run");
            };
            //5.使用相同型別的委託例項賦值
            myDelegate += new MyDelegate(myClass.MyFunc);
            //6.使用-=運算子刪除委託中指定的方法
            myDelegate -= myClass.MyFunc;  //※只能通過此種方式才可以將委託列表中指定方法移除,不能通過myClass = null等方式移除
            
            //執行委託中的方法
            myDelegate();
            //或者通過其Invoke()方法執行委託
            //myDelegate.Invoke();
            //增加非空判斷
            //if (myDelegate != null) myDelegate();
            //或者簡化為(需要C#6.0以上)
            //myDelegate?.Invoke();
            
            Console.ReadKey();
        }
    }

  ※可以將類或結構中任何與委託型別的引數列表和返回值型別相匹配的例項方法和靜態方法賦值給委託例項;

  ※使用匿名方法或Lambda表示式新增的方法不能通過-或-=刪除這些方法;

   ※與直接呼叫方法相比,使用委託呼叫方法用時幾乎沒有差別,使用以下程式碼多次測試:

public delegate void MyDelegate();
public class MyClass
{
    public void MyFunc()
    {
        
    }
}

class Program
{
    static void Main(string[] args)
    {
        MyClass myClass = new MyClass();
        MyDelegate myDelegate = myClass.MyFunc;

        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int i = 0; i < 10_000_000; i++)
        {
            myDelegate();
        }
        stopwatch.Stop();
        Console.WriteLine($"使用委託呼叫1千萬次耗時:{stopwatch.ElapsedMilliseconds}");

        stopwatch.Reset();
        stopwatch.Start();
        for (int i = 0; i < 10_000_000; i++)
        {
            myClass.MyFunc();
        }
        stopwatch.Stop();
        Console.WriteLine($"直接呼叫1千萬次耗時:{stopwatch.ElapsedMilliseconds}");

        Console.Read();
    }
}

 

  二、委託例項是一種特殊型別的物件,其特殊之處在於,其他物件都包含資料,而委託例項中只包含一個或多個方法的引用,也正是因為這個特性,通常用委託例項來傳遞方法,即通過委託例項作為載體將方法作為引數傳遞到另一個方法中,並在方法中的某個時機或稍後呼叫委託例項來執行這個方法,這被稱為非同步回撥,作為引數的這個方法也叫回撥函式(Callback Method);

  1.所有方法都可以直接隱式轉換為對應引數列表和返回值型別的委託:

    public void MyFunc(Action<int> myAction, int myNum)
    {
        if (myAction != null)
        {
            myAction(myNum);
        }
    }
    public void MyAction(int myNum)
    {
        Console.WriteLine("myNum is : " + myNum);
    }
  //使用方式:
    MyFunc(MyAction, 1);  //myNum is : 1

  2.方法不能顯示轉換為object型別,但委託可以,所以可以將方法顯示轉換為委託然後作為引數傳遞:

    public void MyFunc(object myObj, int myNum)
    {
        Action<int> myAction = (Action<int>)myObj;
        myAction(myNum);
    }
    public void MyAction(int myNum)
    {
        Console.WriteLine("myNum is : " + myNum);
    }
  //使用方式:
    MyFunc((Action<int>)MyAction, 1);  //myNum is : 1

 

  三、對於有返回值的多播委託,如果想儲存其返回值列表,可以先獲取委託例項中的方法,然後依次執行這些方法:

    Func<string> myFunc;
    Delegate[] delegateArray = myFunc.GetInvocationList();
    string[] returnArray = new string[delegateArray.Length];
    for (int i = 0; i < delegateArray.Length; i++)
    {
        returnArray[i] = (delegateArray[i] as Func<string>)();
        //returnArray[i] = (string)delegateArray[i].DynamicInvoke();
    }

 

  四、引用System名稱空間後,可以使用系統定義的兩個泛型委託void Action()和TResult Func<TResult>(),其中,Action()為無返回值的委託,Func<TResult>()為有返回值且返回值型別為TResult的委託,兩者都可以擁有多個引數,例如:void Action<T1, T2>(T1 obj1, T2 obj2)和TResult Func<T1, T2, TResult>(T1 obj1, T2 obj2);

  1.宣告一個多個引數的委託型別:

    public Action<string, int> MyAction; //宣告一個無返回值,引數列表為string,int的委託

    public Func<bool, string, int> MyFunc; //宣告一個返回值型別為bool,引數列表為string,int的委託

  2.System名稱空間還提供了一個返回bool值的委託bool Predicate<T>(T obj),通常用於匹配相關的操作;

 

  五、事件(Event)是一種具有一定限制的特殊的多播委託:

  //首先宣告一個委託或使用系統預留的委託型別Action等
  public delegate void MyDelegate();
  //宣告事件,相比宣告委託例項多了一個event關鍵字
  public event MyDelegate MyEvent;

  1.委託和事件的關係類似變數和屬性的關係,委託可以作為區域性變數,也可以作為類的成員,而事件只能作為類的成員;委託可以在任何地方進行賦值(=)和呼叫操作,而事件僅可以從宣告事件的類或結構中對其進行賦值和呼叫操作,在外部只能進行+=新增方法和-=移除方法操作;

  2.通常,委託用於回撥,將方法當做引數傳遞到其他方法中,並在指定的時機呼叫該委託中的方法;事件用於通知,在類或物件中通知其他類或物件以進行相關操作,即接收方將需要響應的方法註冊到源物件的事件上,當源物件發生了某個特定情況時,觸發該事件,此時該事件所註冊的所有方法都會被呼叫;

    public class MyClass
    {
        public delegate void MyDelegate();
        public event MyDelegate MyEvent;
    
        public void OnEvent()
        {
            //只可在當前類中進行呼叫
            MyEvent?.Invoke();
            //只可在當前類中進行賦值
            //MyEvent = null;
        }
    }
    //使用方式:
    MyClass myClass = new MyClass();
    //在外部使用+=給事件新增方法
    myClass.MyEvent += MyFunc;  //方法定義:void MyFunc() { Console.WriteLine("MyFunc run"); }
    myClass.OnEvent();  //MyFunc run

   3.事件中方法的新增和移除是通過事件訪問器實現的,其形式類似屬性訪問器,不同之處在於事件訪問器命名為add和remove,在預設情況下,不需要手動定義事件訪問器,編譯器會自動新增預設的事件訪問器,也可以進行自定義事件訪問器:

  private event MyDelegate myEvent;
  public event MyDelegate MyEvent
  {
    add { myEvent += value; }
    remove { myEvent -= value; }
  }

  4.在使用反射獲取型別的所有方法時,如果型別中包含事件,會獲取事件中的公共訪問器所生成的方法:

  typeof(MyClass).GetMethods(); //add_MyEvent remove_MyEvent ToString Equals GetHashCode GetType

 


如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的認可是我寫作的最大動力!

作者:Minotauros
出處:https://www.cnblogs.com/minotauros/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。

相關文章