【C#】詳解C#委托


目錄結構:

contents structure [+]

1.委托語法

本文會詳細闡述委托的使用,以及實現,想必讀者已經知道了函數編程中的回調機制,C#為回調機制提供了一種簡便語法,這就是委托。
在使用委托之前需要使用delegate關鍵字進行聲明:
比如,

delegate void MyDelegate();

上面定義了一個無參數,無返回的委托。定義好后,然后就可以聲明委托對象。

MyDelegate myDelegate=new MyDelegate(SomeStaticMethod);

上面創建了一個委托對象,這個委托對象關聯了一個方法參數。
C#為這種操作提供了一種糖語法,就是不需要構造委托對象,如下:

MyDelegate myDelegate=SomeStaticMethod;


示例:
 

   class Program
    {
        delegate void MyDelegate();
        static void Main(string[] args)
        {
            MyDelegate myDelegate = Method1;//委托靜態方法Method1
            myDelegate += new Program().Method2;//委托實例方法Method2
            myDelegate.Invoke();//調用委托鏈上的所有方法

            Console.ReadKey();
        }
        static void Method1() {
            Console.WriteLine("method1 invoked");
        }
        void Method2() {
            Console.WriteLine("method2 invoked");
        }
    }

2.泛型委托

C#允許泛型委托,目的是保證任何類型的對象都可以以類型安全的方式傳給回調方法。
例如:

public delegate TResult CallMe<TResult,TKey,TValue>(TKey key,TValue value);

委托的每個泛型類型參數都可以標記為協變量和逆變量。


在這里介紹一下泛型類型參數的三種變量:
不變量(invariant):意味着泛型類類型參數不能更改。
逆變量(contravariant):意味着泛型類型參數可以從一個類更改為它的某個派生類,C#中使用in關鍵字標記逆變量形式的泛型類型參數。逆變量泛型類型參數T只能出現在輸入位置,作為參數。
協變量(convariant):意味着泛型類型參數可以從一個類更改為它的某個基類,C#中使用out關鍵字標記協變量形式的泛型類型參數。逆變量泛型類型參數只能出現在輸出位置,作為返回類型。


例如存在以下泛型類型委托:

public delegate TResult MyFunc<in T,out TResult>(T arg);

如果像下面定義一個委托變量:

MyFunc(Object,ArgumentException) fn1=null;

然后就可以將它轉變為其他委托類型變量:

MyFunc(String,Exception) fn2=fn1;//轉化
fn2("");//調用

3.委托鏈

C#中提供了一種委托鏈的操作,通過委托鏈可以關聯多個方法。
C#在實現委托鏈的時候使用了組合設計模式。C#中使用Delegate類的Combine類來完成委托的鏈接,例如:

    class Program
    {
        delegate Int32 Max(Int32 a, Int32 b);
        static void Main(string[] args)
        {
            Max max1 = new Max(new Program().Method1);//定義委托max1
            Max max2 = new Max(new Program().Method2);//定義委托max2
            Delegate max = Delegate.Combine(max1, max2);//組合委托鏈
            Object obj= max.DynamicInvoke(12,13);//動態調用
            Console.WriteLine(obj);//13
            Console.ReadLine();
        }
        Int32 Method1(Int32 a, Int32 b) {
            return a > b ? a : b;
        }
        Int32 Method2(Int32 a, Int32 b) {
            if (a > b) {
                return a;
            }
            return b;
        }
    }

上面我們使用Delegate的Combine方法合並為委托鏈,其實Delegate類還提供了許多操作方法,例如:

Delegate.remove(Delegate,Delegate);//從第一個委托鏈中移除第二個委托鏈中的委托
Delegate.DynamicInvoke(Object[]);//調用委托鏈中的所有方法
static Delegate CreateDelegate(Type type,MethodInfo method)//從給定的委托類型中,創建一個靜態方法委托

在C#中還給Delegate重載了+=和-=操作符,可以更方便的操作委托鏈,比如:

            Max max1 = new Max(new Program().Method1);//定義委托max1
            Max max2 = new Max(new Program().Method2);//定義委托max2
            max1 += max2;

4.lambda表達式

Lambda表達式是C#3.0才添加的功能,是為委托添加的糖語法,lambda表達式允許以內聯的方式寫回調方法的代碼,而不需要單獨寫到方法中,例如:

        delegate Int32 Max(Int32 a, Int32 b);
        static void Main(string[] args)
        {
            Max m = null;
            m += (a, b) => {
                return a > b ? a : b;
            };//lambda表達式
            Int32 res=  m(10,11);//調用方法
            Console.WriteLine(res);
            Console.ReadLine();
        }

lambda表達式的大致格式為:(參數...)=>{方法主體}
通常我們在寫線程的時候,我們都會定義一個WaitCallback委托,然后再關聯方法,現在我們可以像如下這樣:

            ThreadPool.QueueUserWorkItem((obj) => {
                Console.WriteLine("Thread:" + Thread.CurrentThread.ManagedThreadId);
            });
            Console.ReadLine();

關於使用委托,這里有一個不成文規定,通常若是需要在回調方法寫3行以上的代碼就不用lambda表達式,而是重新定義一個方法,並為其分配名稱。若小於等於3行代碼,就可以使用lambda表達式。

例如:

            //創建並初始化一個String數組
            String[] names = { "Jeff","Kristin","Aidan","Grant"};

            //獲取只含有小寫字母a的名稱
            Char charTOFind='a';
            names = Array.FindAll(names, (name) => { return name.IndexOf(charTOFind) >= 0; });

            //將每個字符串的名稱轉化為大寫
            names= Array.ConvertAll(names, (name) => { return name.ToUpper(); });

            //打印
            Array.ForEach(names,System.Console.WriteLine);

5.揭秘委托

到現在我們已經知道了如何定義委托,以及C#為委托提供的糖語法,接下來我們開始探討一下委托究竟是什么,將如下的代碼編譯為二進制文件,

    class Program
    {
        delegate Int32 MathAdd(Int32 a, Int32 b);
        static void Main(string[] args)
        {
            MathAdd mathAdd = new MathAdd((Int32 a, Int32 b) => { return a + b; });
            Int32 result= mathAdd(3,2);
            Console.WriteLine(result);//5
            Console.ReadKey();
        }
    }

然后我們使用ildasm.exe反編譯應用程序得到il文件,就可以從CLR層面觀察出C#的委托到底是怎么回事了。

通過這張圖片我們可以清晰的觀察出,IL代碼生成了一個靜態內部類MathAdd,然后該類派生於MulticastDelegate,到現在我們清楚了C#的委托本質上就是類。除此之外,該類提供了一個構造器方法,BeginInvoke方法,EndInvoke方法,Invoke方法。

6.類庫中的委托

C#已經預先定義好了豐富的委托類型供開發人員使用,在MSCorLib.dll中,有將近50個委托類型,
例如:

public delegate void TryCode(object userData);
public delegate void WaitCallback(object state);
public delegate void TimerCallback(object state);
public delegate void ContextCallback(object state);
......

上面這些委托都有一個共同點,就是他們都是一樣的,也就是說,只需要定義一個委托就可以了。
.NET Framework支持泛型,C#已經為我們定義好了17個Action委托,其中包含16個泛型委托:

public delegate void Action();
public delegate void Action<T>(T arg);
public delegate void Action<T1,T2>(T1 arg1,T2 arg2);
public delegate void Action<T1,T2,T3>(T1 arg1,T2 arg2,T3 arg3);
......
public delegate void Action<T1,.....,T16>(T1 arg1,......,T16 arg16);

除了Action委托,C#還為我們定義了Func委托,允許回調方法返回值:

public delegate TResult Func<TResult>();
public delegate TResult Func<T,TResult>(T arg);
public delegate TResult Func<T1,T2,TResult>(T1 arg1,T2 arg2);
public delegate TResult Func<T1,T2,T3,TResult>(T1 arg1,T2 arg2,T3 arg3);
......
public delegate TResult Func<T1,.....,T16,TResult>(T1 arg1,.....,T16 arg16);

上面列出了C#定義的Action和Func委托,如果涉及到委托最好是使用這些委托類型,而不是重新定義,這樣可以減少程序集的大小。

但是如果涉及到ref或out參數,那么就只有自己定義了。
比如:

delegate void Bar(ref Int32 z);

7.委托和反射

通常情況下,使用委托都應該知道委托的原型,但是反射提供了另一種可能來使用委托。
在MethodInfo中提供了一個createDelegate方法,運行在編譯器不知道委托的所有信息下提前創建委托。

        public virtual Delegate CreateDelegate(Type delegateType);
        public virtual Delegate CreateDelegate(Type delegateType, object target);

在創建好Delegate對象后,就可以通過調用對象的DynamicInvoke方法來調用委托。

    public Object DynamicInvoke(params Object[] args);

下面的展示了如何使用反射來調用委托:

    class Program
    {
        static void Main(string[] args)
        {
            //定義一個Func<Int32,Int32,Int32>類型的數據
            Type type=typeof(Func<Int32,Int32,Int32>);
            //獲得Program實例的Max方法
            MethodInfo methodInfo = typeof(Program).GetMethod("Max", BindingFlags.Instance|BindingFlags.Public);
            //創建委托
            Delegate d = methodInfo.CreateDelegate(type, new Program());
            //調用
            Object res = d.DynamicInvoke(13, 12);
            Console.WriteLine(res);//13
            Console.ReadKey();
        }
        public Int32 Max(Int32 a, Int32 b)
        {
            return a > b ? a : b;
        }
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM