歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> C#方法組轉換和匿名方法

C#方法組轉換和匿名方法

日期:2017/3/1 9:32:01   编辑:Linux編程

前面的文章介紹過,C# 1.0中出現委托這個核心概念,在C# 2.0中,委托得到了很大的改進。C# 2.0中委托的改進為C# 3.0中的新特性提供了鋪墊,當我們了解了匿名方法後,Lambda的學習就會變得相對容易。

  • C#委托的基本概念 http://www.linuxidc.com/Linux/2015-02/113470.htm
  • 進一步理解C#委托 http://www.linuxidc.com/Linux/2015-02/113469.htm

下面就看看C# 2.0中委托的改進。

方法組轉換

在C# 1.0中,如果要創建一個委托實例,就必須同時指定委托類型和符合委托簽名的方法。但是,在C# 2.0中,支持了方法組轉換,也就是說我們可以從方法組到一個兼容委托類型的隱式轉換。所謂"方法組"(method group),其實就是一個方法名。

看一個例子:

class Program
{
    public delegate void ReverseStringHandler(string str);

    private static void ReverseString(string str)
    {
        char[] charArray = str.ToCharArray();
        Array.Reverse(charArray);
        Console.WriteLine(charArray);
    }

    static void Main(string[] args)
    {
        //C# 1.0中創建委托實例
        ReverseStringHandler reverseStringHandler = new ReverseStringHandler(ReverseString);
        reverseStringHandler("Hello World");

        //C# 2.0中通過方法組轉換創建委托實例
        reverseStringHandler = ReverseString;
        reverseStringHandler("Good morning");
    }
}

通過方法組轉換,繁瑣的委托實例創建得到了簡化。

匿名方法

根據C# 1.0中了解到的知識,當我們創建一個委托實例的時候,我們需要找到一個跟委托類型簽名一致的方法,用這個方法來實例化一個委托對象。

看看前面的字符串反轉的例子,可能"ReverseString"這個方法在程序中只會使用一次,但是為了創建委托實例,這個方法必須要存在。

在C# 2.0中引入了匿名方法,所以有了更簡單的方式實現上面的例子:

public delegate void ReverseStringHandler(string str);

static void Main(string[] args)
{
    ReverseStringHandler reverseStringHandler = delegate(string str)
    {
        char[] charArray = str.ToCharArray();
        Array.Reverse(charArray);
        Console.WriteLine(charArray);
    };
    reverseStringHandler("Hello World");

    Console.Read();
}

從上面可以看到,匿名方法就是通過delegate關鍵字以及參數列表和具體語句塊的實現。

所以說,如果用來實例化委托的方法比較簡單,並且這個方法在其他地方使用的頻率很低時,這時候就可以考慮用匿名方法來進行簡化。

匿名方法工作原理

如果你對匿名方法仍有疑惑,建議你看看上面例子的IL代碼。

在Main函數的IL代碼中,可以看到編譯器為我們生成了一個名為"<Main>b__0"的方法,方法接受一個string類型的參數。

.method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    ……
    IL_0009: ldftn void AnonymousMethod.Program::'<Main>b__0'(string)
    ……
} // end of method Program::Main

當我們查看"<Main>b__0"方法的IL代碼後,可以看到這個方法就是我們在匿名方法的語句塊中定義的操作。

.method private hidebysig static 
    void '<Main>b__0' (
        string str
    ) cil managed 
{
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
        01 00 00 00
    )
    // Method begins at RVA 0x2050
    // Code size 23 (0x17)
    .maxstack 1
    .locals init (
        [0] char[] charArray
    )

    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: callvirt instance char[] [mscorlib]System.String::ToCharArray()
    IL_0007: stloc.0
    IL_0008: ldloc.0
    IL_0009: call void [mscorlib]System.Array::Reverse(class [mscorlib]System.Array)
    IL_000e: nop
    IL_000f: ldloc.0
    IL_0010: call void [mscorlib]System.Console::WriteLine(char[])
    IL_0015: nop
    IL_0016: ret
} // end of method Program::'<Main>b__0'

匿名方法的實現原理就是:編譯器將在匿名方法所在的類,為每個匿名方法都創建了一個方法。編譯器創建的這些方法只在IL代碼中有效,在C#代碼中是無效的,所以C#代碼不能直接使用這些方法。

其實,匿名方法更常用的地方是把匿名方法當作一個參數傳遞給另一個方法。大家肯定都知道List有一個FindAll的方法來查找符合條件的item,這裡FindAll的參數就是一個過濾條件的委托。

List<int> numList = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
foreach(int num in numList.FindAll(delegate(int n){return n>6;}))
{
    Console.WriteLine(num);
}

說到了這裡,我們就看看系統幫我們定義的委托,在C#中,Action、Func和Predicate是系統定義的委托,我們可以直接使用。上面的FindAll的參數就是一個Predicate<T>的泛型委托。

Action委托

Action<T>是無返回值的泛型委托,Action委托可以支持至少0個參數,至多16個參數。

例如:

  • Action 表示無參,無返回值的委托
  • Action<int,string> 表示有傳入參數int,string無返回值的委托
  • Action<int,string,bool> 表示有傳入參數int,string,bool無返回值的委托

對於前面的字符串反轉的例子,我們可以使用Action委托進一步簡化,這樣我們連"ReverseStringHandler"這個委托也省略了:

static void Main(string[] args)
{
   Action<string> reverseString = delegate(string str)
    {
        char[] charArray = str.ToCharArray();
        Array.Reverse(charArray);
        Console.WriteLine(charArray);
    };
   reverseString("Hello World");

    Console.Read();
}

Func委托

前面看到Action委托是沒有返回值的,為了解決我們有時可能需要返回值的問題,系統中又出現了Func委托。

Func<TResult>是有返回值的泛型委托,其中TResult就代表返回值的類型()。Func委托可以支持至少0個參數,至多16個參數(Func<T1,T2,…,T16,TResult>)。

例如:

  • Func<int> 表示無參,返回值為int的委托
  • Func<object,string,int> 表示傳入參數為object, string 返回值為int的委托
  • Func<object,string,int> 表示傳入參數為object, string 返回值為int的委托

看個簡單的例子:

Func<int, int, int> addOperation = delegate(int numA, int numB) { return numA + numB; };
Console.WriteLine(addOperation(3,4));

Predicate委托

predicate 是返回bool型的泛型委托,常常結合集合類的查詢使用;Predicate有且只有一個參數,返回值固定為bool。

Predicate原型:public delegate bool Predicate<T> (T obj)

  • T: 要比較的對象的類型。
  • obj: 要按照由此委托表示的方法中定義的條件進行比較的對象。
  • 返回值:如果 obj 符合由此委托表示的方法中定義的條件,則為 true;否則為 false。

在前面結合查詢的例子中,我們直接把匿名方法"delegate(int n){return n>6;}"傳給了FindAll方法。

其實也可以寫成,

Predicate<int> checkNum = delegate(int n) { return n > 6; };
foreach (int num in numList.FindAll(checkNum)) {……}

忽略委托參數

在有些情況下,我們並不需要委托參數,那麼匿名方法可以進一步省略參數列表,只需要使用一個delegate關鍵字,加上作為方法的操作使用的代碼塊。

看一個簡單的代碼段:

Action printer = delegate { Console.WriteLine("Hello world"); };
printer();

注意,這個"參數通配"(paremeter wildcarding)的特性並不能適用所有的情況,如果匿名方法能夠轉換成多個委托類型,那麼我們就需要給編譯器提供更多的信息

舉個例子,線程的構造函數設計兩個委托類型,一個有參數,一個無參數。

public delegate void ParameterizedThreadStart(object obj)
public delegate void ThreadStart()

所以,當我們通過下面的語句創建線程的時候,前兩條語句沒有問題,但是第三條語句會有一個錯誤。

因為第三條語句中的匿名方法可以轉換成多個委托類型,編譯器就不知道怎麼處理了,所以,我們需要顯示給出參數列表。

Thread t1 = new Thread(delegate() { Console.WriteLine("this is t1"); });
Thread t2 = new Thread(delegate(object o) { Console.WriteLine("this is t2"); });
Thread t3 = new Thread(delegate { Console.WriteLine("this is t3"); });

總結

本篇文章介紹了C# 2.0中委托的改進,通過方法組轉換和匿名方法,可以簡化程序。

同時,看到了系統定義的三個委托類型,所以有些時候我們可以不用創建自己的委托;但是要這個權衡,如果我們要經常使用一個特定類型的委托,那還是建議定義一個有意義更加明顯的委托類型。

下一篇我們將一起看看匿名方法中的變量,(見 http://www.linuxidc.com/Linux/2015-02/114152.htm)。

C#多線程編程實例 線程與窗體交互【附源碼】 http://www.linuxidc.com/Linux/2014-07/104294.htm

C#數學運算表達式解釋器 http://www.linuxidc.com/Linux/2014-07/104289.htm

在C語言中解析JSON配置文件 http://www.linuxidc.com/Linux/2014-05/101822.htm

C++ Primer Plus 第6版 中文版 清晰有書簽PDF+源代碼 http://www.linuxidc.com/Linux/2014-05/101227.htm

Copyright © Linux教程網 All Rights Reserved