委派和 Lambda

委派會定義類型,代表具有特定參數清單及傳回型別的方法參考。 參數清單與傳回型別符合的方法 (靜態或執行個體) 可指派給該類型的變數,然後直接呼叫 (使用適當的引數),或當做引數本身傳遞至另一個方法,再進行呼叫。 下列範例示範委派的用法。

using System;
using System.Linq;

public class Program
{
    public delegate string Reverse(string s);

    static string ReverseString(string s)
    {
        return new string(s.Reverse().ToArray());
    }

    static void Main(string[] args)
    {
        Reverse rev = ReverseString;

        Console.WriteLine(rev("a string"));
    }
}
  • public delegate string Reverse(string s); 行會建立方法的委派類型,以接受字串參數再傳回字串參數。
  • static string ReverseString(string s) 方法與定義的委派類型具有完全相同的參數清單與傳回型別,會實作委派。
  • Reverse rev = ReverseString; 程式行顯示您可將方法指派至對應委派類型的變數。
  • Console.WriteLine(rev("a string")); 程式行示範如何使用委派類型的變數來叫用委派。

為了簡化開發程序,.NET 包含一組委派類型,程式設計人員可重複使用這些類型,而不需要建立新的類型。 這些類型是 Func<>Action<>Predicate<>,且不需要定義新的委派類型即可使用。 這三種類型之間有一些差異,這些類型必須以預期的方式使用:

  • 使用委派的引數時如需執行動作,會使用 Action<>。 其封裝的方法不會傳回值。
  • Func<> 通常會在需要轉換時使用,亦即您必須將委派的引數轉換成其他結果。 投影是不錯的範例。 其封裝的方法會傳回指定的值。
  • Predicate<> 會在需要判斷引數是否符合委派的條件時使用。 其也可以寫入為 Func<T, bool>,這表示方法會傳回布林值。

我們現在可以使用 Func<> 委派取代自訂類型,針對上述範例進行重寫。 程式會以完全相同的方式繼續執行。

using System;
using System.Linq;

public class Program
{
    static string ReverseString(string s)
    {
        return new string(s.Reverse().ToArray());
    }

    static void Main(string[] args)
    {
        Func<string, string> rev = ReverseString;

        Console.WriteLine(rev("a string"));
    }
}

在這個簡單的範例中,在 Main 方法外定義方法似乎有點多餘。 .NET Framework 2.0 引進了匿名委派的概念,可讓您建立「內嵌」委派,而不需要指定任何其他類型或方法。

在下列範例中,匿名委派只會將清單篩選為偶數,然後將其列印到主控台。

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main(string[] args)
    {
        List<int> list = new List<int>();

        for (int i = 1; i <= 100; i++)
        {
            list.Add(i);
        }

        List<int> result = list.FindAll(
          delegate (int no)
          {
              return (no % 2 == 0);
          }
        );

        foreach (var item in result)
        {
            Console.WriteLine(item);
        }
    }
}

如您所見,委派的主體只是一組運算式,與任何其他委派相同。 但這並不是不同的定義,而是當作「臨機操作」引入 List<T>.FindAll 方法呼叫。

不過,即使使用此方法,還是有許多程式碼可以捨棄。 此時就需要 Lambda 運算式。 Lambda 運算式 (簡稱 "Lambda") 是在 C# 3.0 中,當作 Language Integrated Query (LINQ) 的其中一個核心建置組塊所引入。 這是更方便使用委派的語法。 這些運算式可宣告參數清單和方法主體,但除非指派給委派,否則並沒有自己的正式身分識別。 不同於委派,這些運算式可在事件註冊右邊,或在各種 LINQ 子句和方法中直接指派。

因為 Lambda 運算式不過是指定委派的另一種方式,所以我們應該能夠重寫上述範例,使用 Lambda 運算式取代匿名委派。

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main(string[] args)
    {
        List<int> list = new List<int>();

        for (int i = 1; i <= 100; i++)
        {
            list.Add(i);
        }

        List<int> result = list.FindAll(i => i % 2 == 0);

        foreach (var item in result)
        {
            Console.WriteLine(item);
        }
    }
}

在上述範例中,使用的 Lambda 運算式為 i => i % 2 == 0。 再次強調,這是方便使用委派的語法。 實際上發生的情況與使用匿名委派所發生的情況類似。

同樣地,Lambda 就是委派,這表示它們可當做事件處理常式使用,而不會有任何問題,如下列程式碼片段所示。

public MainWindow()
{
    InitializeComponent();

    Loaded += (o, e) =>
    {
        this.Title = "Loaded";
    };
}

在此內容中使用 += 運算子來訂閱事件。 如需詳細資訊,請參閱如何訂閱及取消訂閱事件

延伸閱讀和資源