C# 委託(delegate)、泛型委託和Lambda表示式

willingtolove發表於2019-07-31

# 什麼是委託

1、從資料結構來講,委託是和類一樣是一種使用者自定義型別。
2、委託是方法的抽象,它儲存的就是一系列具有相同引數和返回型別的方法的地址。呼叫委託的時候,委託包含的所有方法將被執行。

# 委託宣告、例項化和呼叫

1、宣告

委託是一種特殊的類,因此委託的宣告與類的宣告方法類似,在任何可以宣告類的地方都可以宣告委託。委託宣告用delegate關鍵字,同時委託要指明方法引數和返回值,寫法與方法類似。綜合類的宣告和方法的宣告,委託宣告寫成如下形式:

[訪問修飾符] delegate 返回值型別 委託名 (形參列表);

public delegate void MyDel();//定義了一個委託MyDel,它可以註冊返回void型別且沒有引數的函式
public delegate void MyDel1(string str);//定義了一個委託MyDel1,它可以註冊返回void型別且有一個string作為引數的函式
public delegate int MyDel2(int a,int b);//定義了一個委託MyDel2,它可以註冊返回int型別且有兩個int作為引數的函式

2、委託的例項化

與普通類的使用方法相同,宣告瞭委託之後,我們必須給委託傳遞一個具體的方法,才能在執行時呼叫委託例項。委託例項包含了被傳遞給它的方法的資訊,在執行時,呼叫委託例項就相當於執行它當中的方法。

委託例項化格式如下:

委託類名 委託例項名 = new 委託類名(Target) ;

其中,委託例項名是自定義的名稱,Target是要傳入的方法的名稱。注意,Target是方法的引用,不能帶()。帶()的話是該方法的呼叫。區分引用和呼叫。
委託的例項化還有一種簡單的方法:

委託類名 委託例項名 = Target;

在需要委託例項的地方直接傳入Target引用即可,C#編譯器會自動根據委託型別進行驗證,這稱為“委託推斷”。

MyDel2 testDel=new MyDel2(Add);
MyDel2 testDel1 = Add;

3、委託例項的呼叫

委託例項等價於它當中實際方法,因此可以使用反射的Invoke()方法呼叫委託例項,也可以直接在委託例項後加上()進行呼叫。

int num = testDel(1,2);
int num1 = testDel.Invoke(1, 2);

4、委託完整的簡單示例

namespace delegateTest
{
    public delegate int MyCalculator(int num1, int num2);
    class Program
    {
        static void Main(string[] args)
        {
            MyCalculator myCal=new MyCalculator(Add);
            int addNum= myCal(1,2);

            MyCalculator myCal1 = Sub;
            int subNum = myCal1.Invoke(1, 2);

            Console.WriteLine("addNum:{0},subNum:{1}", addNum, subNum);
            
            int calNum = Calculate(1, 2, Add);
            Console.WriteLine("calNum:{0}", calNum);
        }

        static int Add(int num1, int num2)
        {
            Console.WriteLine("num1 + num2={0}",num1 + num2);
            return num1 + num2;
        }
        static int Sub(int num1, int num2)
        {
            Console.WriteLine("num1 - num2={0}", num1 - num2);
            return num1 - num2;
        }
        static int Calculate(int num1,int num2,MyCalculator calDel)
        {
            return calDel(num1,num2);
        }
    }
}

控制檯列印結果:

num1 + num2=3
num1 - num2=-1
addNum:3,subNum:-1
num1 + num2=3
calNum:3

#泛型委託

我們每次要使用一個委託時,都需要先宣告這個委託類,規定引數和返回值型別,然後才能例項化、呼叫。為了簡化這個過程, .NET 框架為我們封裝了三個泛型委託類,因此大部分情況下我們不必再宣告委託,可以拿來直接例項化使用,方便了我們的日常寫程式碼。
這三種泛型委託包括:Func委託、Action委託和Predicate委託。

1、Func委託

Func委託代表著擁有返回值的泛型委託。Func有一系列的過載,形式如 Func<T1,T2, ... TResult>,其中TResult代表委託的返回值型別,其餘均是引數型別。只有一個T時,即Func,代表該委託是無引數的。.NET封裝了最多16個輸入引數的Funct<>委託。
需要特別注意的是,若方法沒有返回值,即返回 void ,由於 void 不是資料型別,因此不能定義Func委託。返回 void 的泛型委託見下文的Action
Func的使用方法與一般的委託相同。例如上面的案例可改寫如下:

namespace delegateTest
{
    class Program
    {
        static void Main(string[] args)
        {
            int calNum = Calculate(1, 2, Sub);
            Console.WriteLine("calNum:{0}", calNum);// -1
        }
        static int Calculate(int num1, int num2, Func<int, int, int> calDel)
        {
            return calDel(num1,num2);
        }
        static int Sub(int num1, int num2)
        {
            Console.WriteLine("num1 - num2={0}", num1 - num2);
            return num1 - num2;
        }
    }
}

2、Action委託

Action委託代表返回值為空 void 的委託,它也有一些列過載,最多擁有16個輸入引數。用法與Func相同。

namespace delegateTest
{
    class Program
    {
        static void Main(string[] args)
        {
            DoSome("hello",Say);// hello
        }
        static void DoSome(string str,Action<string> doAction)
        {
            doAction(str);
        }
        static void Say(string str)
        {
            Console.WriteLine(str);
        }
    }
}

3、Predicate委託

這個一般用的較少,它封裝返回值為bool型別的委託,可被Func代替。

#匿名委託

採用匿名方法例項化的委託稱為匿名委託。
每次例項化一個委託時,都需要事先定義一個委託所要呼叫的方法。為了簡化這個流程,C# 2.0開始提供匿名方法來例項化委託。這樣,我們在例項化委託時就可以 “隨用隨寫” 它的例項方法。
使用的格式是:

委託類名 委託例項名 = delegate (args) { 方法體程式碼 } ;

這樣就可以直接把方法寫在例項化程式碼中,不必在另一個地方定義方法。當然,匿名委託不適合需要採用多個方法的委託的定義。
使用匿名方法,以上程式碼可改寫為:

MyCalculator myCal2 = delegate(int num1, int num2)
{
    //列印匿名方法的名字
    Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name);  // <Main>b__0
    return num1 + num2;
};
int num11= myCal2(1,2);//3

需要說明的是,匿名方法並不是真的“沒有名字”的,而是編譯器為我們自動取一個名字。

#Lambda表示式

縱然匿名方法使用很方便,可惜她很快就成了過氣網紅,沒能領多長時間的風騷。如今已經很少見到了,因為delegate關鍵字限制了她用途的擴充套件。自從C# 3.0開始,她就被Lambda表示式取代,而且Lambda表示式用起來更簡單。Lambda表示式本質上是改進的匿名方法。

1、表示式Lambda

當匿名函式只有一行程式碼時,可採用這種形式。例如:

MyCalculator myCal = (num1, num2) =>  num1 + num2;
int num = myCal(1, 2);// 3

其中=>符號代表Lambda表示式,它的左側是引數,右側是要返回或執行的語句。引數要放在圓括號中,若只有一個引數,為了方便起見可省略圓括號。有多個引數或者沒有引數時,不可省略圓括號。
相比匿名函式,在表示式Lambda中,方法體的花括號{}和return關鍵字被省略掉了。

2、語句Lambda

當匿名函式有多行程式碼時,只能採用語句Lambda。

MyCalculator myCal = (int num1, int num2)=>
{
    Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name);
    return num1 + num2;
};
int num = myCal(1, 2);// 3

語句Lambda不可以省略{}和return語句。

3、Lambda的主要用處

實際中用到Lambda表示式的地方大都是委託,例如linq的對集合類的擴充套件查詢方法;
很多架構的搭建需要呼叫自定義方法,也離不開委託;
事件機制是基於委託的;
等等。

#多播委託

例項化委託時必須將一個匹配函式註冊到委託上來例項化一個委託物件,但是一個例項化委託不僅可以註冊一個函式還可以註冊多個函式,註冊多個函式後,在執行委託的時候會根據註冊函式的註冊先後順序依次執行每一個註冊函式。
函式註冊委託的原型:

+=new ();

注意:委託必須先例項化以後,才能使用+=註冊其他方法。如果對註冊了函式的委託例項從新使用=號賦值,相當於是重新例項化了委託,之前在上面註冊的函式和委託例項之間也不再產生任何關係。 有+=註冊函式到委託,也有-=解除註冊;

-=new ();

注意:如果在委託註冊了多個函式後,如果委託有返回值,那麼呼叫委託時,返回的將是最後一個註冊函式的返回值。

MyCalculator multiCal=new MyCalculator(Add);
multiCal += Sub;
int num1 = multiCal(1, 2); // -1
multiCal -= Sub;
int num2 = multiCal(1, 2); // 3

#參考:

https://blog.csdn.net/wnvalentin/article/details/81840339


相關文章