C#中的Action、Func委託和Lambda表達式

使用Action、Func和Lambda表達式
在.NET在,我們經常使用委託,委託的作用不必多說,在.NET 2.0之前,我們在使用委託之前,得自定義一個委託類型,再使用這個自定義的委託類型定義一個委託字段或變量。.NET 2.0給我們帶來了Action、Func兩個泛型委託,.NET3.0給我們帶來了Lambda,這一切使得委託的定義和使用變得簡單起來。下面的例子中的委託都使用了Lambda表達式。
一.Action系列的泛型委託
Action系列的委託定義的是沒有返回值(返回值爲void)的委託。它有多個版本包括沒有輸入參數,1個輸入參數,2個輸入參數,3個輸入參數,4個輸入參數共5個版本這幾個版本的原型如下:
1.       沒有輸入參數返回值爲void的委託.
Action委託 封裝一個方法,該方法不採用參數並且不返回值。
可以使用此委託以參數形式傳遞一個執行某操作的方法,而不用顯式聲明一個自定義的委託來封裝此方法。該封裝的方法必須與此委託定義的方法簽名相對應。這意味着該方法不得具有參數和返回值。例:
using System;
using System.Windows.Forms;
public class Name
{
   private string instanceName;
   public Action ShowName;
   public Show()
{
   If(ShowName != null)
    ShowName();
}
   public Name(string name)
   {
      this.instanceName = name;
   }
   public void DisplayToConsole()
   {
      Console.WriteLine(this.instanceName);
   }
   public void DisplayToWindow()
   {
      MessageBox.Show(this.instanceName);
   }
}
public class ActionStudy
{
   public static void Main()
   {
      Name testName = new Name("Koani");
      testName.ShowName  = () => testName.DisplayToWindow();
      testName.Show();
   }
}
2.       有1個輸入參數返回值爲void的委託
Action泛型委託封裝一個方法,該方法只採用一個參數並且不返回值。
可以使用此委託以參數形式傳遞方法,而不用顯式聲明自定義的委託。該方法必須與此
委託定義的方法簽名相對應。也就是說,封裝的方法必須具有一個通過值傳遞給它的參數,並且不能返回值。例:
using System;
using System.Windows.Forms;
public class ActionStudy
{
   public static void Main()
   {
      Action messageTarget;
      if (Environment.GetCommandLineArgs().Length > 1)
         messageTarget = s => MessageBox.Show(s);
      else
         messageTarget = s => Console.WriteLine(s);
      messageTarget("Hello, World!");
   }
}
下面的示例演示如何使用 Action(T) 委託來打印 List(T) 對象的內容。在此示例中,使用 Print 方法將列表的內容顯示到控制檯上。此外,C# 示例還演示如何使用匿名方法將內容顯示到控制檯上。
using System;
using System.Collections.Generic;
class Program
{
    static void Main()
    {
        Action PrintInConsole = s => Console.WriteLine(s);
        Action PrintInDialog = s=>MessageBox.Show(s);
        List names = new List();
        names.Add("Bruce");
        names.Add("Alfred");
        names.Add("Tim");
        names.Add("Richard");
        names.ForEach(PrintInConsole);
        names.ForEach(PrintInDialog);     
    }
}
3.       有2個輸入參數返回值爲void的委託
Action 封裝一個方法,該方法具有兩個參數並且不返回值。
可以使用 Action(T1, T2) 委託以參數形式傳遞方法,而不用顯式聲明自定義的委託。該
方法必須與此委託定義的方法簽名相對應。也就是說,封裝的方法必須具有兩個均通過值傳遞給它的參數,並且不能返回值。
using System;
using System.IO;
public class ActinStudy
{
   public static void Main()
  {
      string message1 = "The first line of a message.";
      string message2 = "The second line of a message.";
      Action  concat;
      if (Environment.GetCommandLineArgs().Length > 1)
         concat = (s1, s2) =>
{
StreamWriter writer = null;
      try
      {
         writer = new StreamWriter(Environment.GetCommandLineArgs()[1], false);
         writer.WriteLine("{0}"n{1}", s1, s2);
      }
      catch
      {
         Console.WriteLine("File write operation failed...");
      }
      finally
      {
         if (writer != null) writer.Close();
      }
};
      else
         concat = (s1, s2) => Console.WriteLine("{0}"n{1}", s1, s2);
      concat(message1, message2);
   }
4.       有3個輸入參數返回值爲void的委託
Action委託,封裝一個方法,該方法採用三個參數並且不返回值。
可以使用 Action(T1, T2, T3) 委託以參數形式傳遞方法,而不用顯式聲明自定義的委託。
該方法必須與此委託定義的方法簽名相對應。也就是說,封裝的方法必須具有三個均通過值傳遞給它的參數,並且不能返回值。
5.       有4個輸入參數返回值爲void的委託
Action委託, 封裝一個方法,該方法具有四個參數並且不返回值。
可以使用 Action(T1, T2, T3, T4) 委託以參數形式傳遞方法,而不用顯式聲明自定義的委託。封裝的方法必須與此委託定義的方法簽名相對應。也就是說,封裝的方法必須具有四個均通過值傳遞給它的參數,並且不能返回值。
二.Func系統的泛型委託
Func系列的委託定義的是返回值的委託。它有多個版本包括沒有輸入參數,1個輸入參數,2個輸入參數,3個輸入參數,4個輸入參數共5個版本這幾個版本的原型如下:
1.       沒有輸入參數有返回值(返回值不爲void)的委託
Func封裝一個不具有參數但卻返回 TResult 參數指定的類型值的方法。
可以使用此委託構造一個能以參數形式傳遞的方法,而不用顯式聲明自定義的委託。該
方法必須與此委託定義的方法簽名相對應。這意味着封裝的方法不得具有參數,但必須返回值。
2.       具有一個輸入參數有返回值(返回值不爲void)的委託
   Func封裝一個具有一個參數並返回 TResult 參數指定的類型值的方法。
可以使用此委託構造一個能以參數形式傳遞的方法,而不用顯式聲明自定義的委託。該方法必須與此委託定義的方法簽名相對應。也就是說,封裝的方法必須具有一個通過值傳遞給它的參數,並且必須返回值。
3.       具有二個輸入參數有返回值(返回值不爲void)的委託
  Func封裝一個具有一個參數並返回 TResult 參數指定的類型值的方法。
可以使用此委託構造一個能以參數形式傳遞的方法,而不用顯式聲明自定義的委託。該方法必須與此委託定義的方法簽名相對應。也就是說,封裝的方法必須具有兩個均通過值傳遞給它的參數,並且必須返回值
4.       具有三個輸入參數有返回值(返回值不爲void)的委託
   Func封裝一個具有三個參數並返回 TResult 參數指定的類型值的方法。
可以使用此委託構造一個能以參數形式傳遞的方法,而不用顯式聲明自定義的委託。該方法必須與此委託定義的方法簽名相對應。也就是說,封裝的方法必須具有三個均通過值傳遞給它的參數,並且必須返回值。
5.       具有四個輸入參數有返回值(返回值不爲void)的委託
 Func封裝一個具有四個參數並返回 TResult 參數指定的類型值的方法。
可以使用此委託構造一個能以參數形式傳遞的方法,而不用顯式聲明自定義的委託。該方法必須與此委託定義的方法簽名相對應。也就是說,封裝的方法必須具有四個均通過值傳遞給它的參數,並且必須返回值。
 
三,Lambda表達式
  C#2.0介紹了一個新特性--匿名方法,允許研發者在線(inline)聲明自己的函數代碼而無須使用委託函數(delegate function)。C#3.0中提供了一個新特性--Lambda表達式,他提供了完成相同目標的更加簡潔的格式。讓我們在討論Lambda表達式以前仔細研究一下匿名方法。 

  匿名方法

  假設你需要創建一個按鈕,當點擊他的時候更新ListBox裏的內容。在C#1.0和1.1裏,你要這樣做:

public MyForm()
{
 listBox = new ListBox(...);
 textBox = new TextBox(...);
 addButton = new Button(...);
 addButton.Click += new EventHandler(AddClick);
}

void AddClick(object sender, EventArgs e)
{
 listBox.Items.Add(textBox.Text);
}

  在C#2.0裏,你需要這樣做:

public MyForm()
{
 listBox = new ListBox(...);
 textBox = new TextBox(...);
 addButton = new Button(...);
 addButton.Click += delegate
 {
  listBox.Items.Add(textBox.Text);
};

  就像你看到的相同,你不必要特別的聲明一個新方法來將他連接到一個事件上。你能在C#2.0裏使用匿名方法來完成同樣的工作。C#3.0裏介紹了一種更加簡單的格式,Lambda表達式,你能直接使用"=>"來書寫你的表達式列表,後面跟上一個表達式或語句塊。

  Lambda表達式中的參數

  Lambda表達式中的參數能是顯式或隱式類型的。在一個顯式類型參數列表裏,每個表達式的類型是顯式指定的。在一個隱式類型參數列表裏,類型是通過上下文推斷出來的:

(int x) => x + 1 // 顯式類型參數
(y,z) => return y * z; // 隱式類型參數

  Lambda演算實例

  下面的例子給出了兩種不同的方法來打印出一個list中長度爲偶數的字符串。第一種方法AnonMethod使用了匿名方法,第二種LambdaExample則是通過Lambda演算實現:

// Program.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Query;
using System.Xml.XLinq;
using System.Data.DLinq;

namespace LambdaExample
{
 public delegate bool KeyValueFilter(K key, V value);
 static class Program
 {

  static void Main(string[] args)
  {
   List list = new List();

   list.Add("AA");
   list.Add("ABC");
   list.Add("DEFG");
   list.Add("XYZ");
   Console.WriteLine("Through Anonymous method");
   AnonMethod(list);
   Console.WriteLine("Through Lambda expression");
   LambdaExample(list);

   Dictionary varClothes= new Dictionary();

   varClothes.Add("Jeans", 20);
   varClothes.Add("Shirts", 15);
   varClothes.Add("Pajamas", 9);
   varClothes.Add("Shoes", 9);
   var ClothesListShortage = varClothes.FilterBy((string name,
   int count) => name == "Shoes" && count < 10);
   // example of multiple parameters
   if(ClothesListShortage.Count > 0)
    Console.WriteLine("We are short of shoes");
   Console.ReadLine();
 }

 static void AnonMethod(List list)
 {
  List evenNumbers = list.FindAll(delegate(string i)
  { return (i.Length % 2) == 0; });
  foreach (string evenNumber in evenNumbers)
  {
   Console.WriteLine(evenNumber);
  }
 }

 static void LambdaExample(List list)
 {
  var evenNumbers = list.FindAll(i =>(i.Length % 2) == 0); // example of single parameter
  foreach(string i in evenNumbers)
  {
   Console.WriteLine(i);
  }
 }
}

public static class Extensions
{
 public static Dictionary FilterBy
(this Dictionary items, KeyValueFilter filter)
 {
  var result = new Dictionary();
  foreach(KeyValuePair element in items)
  {
   if (filter(element.Key, element.Value))
    result.Add(element.Key, element.Value);
  }
  return result;
 }
 
}
}

  如果你安裝了Visual Studio 2005 and LinQ Preview,你能使用編輯器來編譯程式。如果沒有的話,能使用命令行方式:

C:\Program Files\LINQ Preview\Bin\Csc.exe
/reference:"C:\Program Files\LINQ Preview\Bin\System.Data.DLinq.dll"
/reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Data.dll
/reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.dll
/reference:"C:\Program Files\LINQ Preview\Bin\System.Query.dll"
/reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll
/reference:"C:\Program Files\LINQ Preview\Bin\System.Xml.XLinq.dll"
/target:exe Program.cs

  中間語言結果顯示

  打開ILDASM我們來查看一下程式,你將看到如圖1所示的內容:


圖1

  雙擊AnonMethod函數你將看到C#編譯器產生的中間語言代碼:

.method private hidebysig static void AnonMethod(class
[mscorlib]System.Collections.Generic.List`1 list)
cil managed
{
 // Code size 96 (0x60)
 .maxstack 4
 .locals init ([0] class [mscorlib]System.Collections.Generic.List
 `1 evenNumbers,
 [1] string evenNumber,
 [2] valuetype [mscorlib]System.Collections.Generic.List
 `1/Enumerator CSCODE_REPLACEMENT 000,
 [3] bool CSCODE_REPLACEMENT 001)
 IL_0000: nop
 IL_0001: ldarg.0
 IL_0002: ldsfld class [mscorlib]System.Predicate
 `1 LambdaExample.Program::
 `<>9__CachedAnonymousMethodDelegate1
 IL_0007: brtrue.s IL_001c
 IL_0009: ldnull
 IL_000a: ldftn bool LambdaExample.Program::
 `b__0(string)
 IL_0010: newobj instance void class [mscorlib]System.Predicate
 `1::.ctor(object, native int)
 IL_0015: stsfld class [mscorlib]System.Predicate`1
 LambdaExample.Program::
 `<>9__CachedAnonymousMethodDelegate1
 IL_001a: br.s IL_001c
 IL_001c: ldsfld class [mscorlib]System.Predicate`1
 LambdaExample.Program::<>
 9__CachedAnonymousMethodDelegate1
 IL_0021: callvirt instance class [mscorlib]System.Collections.
 Generic.List`1 class [mscorlib]System.
 Collections.Generic.List`1::
 FindAll(class [mscorlib]System.Predicate`1)
 IL_0026: stloc.0
 IL_0027: nop
 IL_0028: ldloc.0
 IL_0029: callvirt instance valuetype [mscorlib]System.Collections.
 Generic.List`1/Enumerator class
 [mscorlib]System.Collections.Generic.List`1
 ::GetEnumerator()
 IL_002e: stloc.2
 .try
 {
  IL_002f: br.s IL_0042
  IL_0031: ldloca.s CSCODE_REPLACEMENT 000
  IL_0033: call instance !0 valuetype [mscorlib]System.
  Collections.Generic.List`1/Enumerator
  ::get_Current()
  IL_0038: stloc.1
  IL_0039: nop
  IL_003a: ldloc.1
  IL_003b: call void [mscorlib]System.Console::
  WriteLine(string)
  IL_0040: nop
  IL_0041: nop
  IL_0042: ldloca.s CSCODE_REPLACEMENT 000
  IL_0044: call instance bool valuetype [mscorlib]System.
  Collections.Generic.List`1/Enumerator
  ::MoveNext()
  IL_0049: stloc.3
  IL_004a: ldloc.3
  IL_004b: brtrue.s IL_0031
  IL_004d: leave.s IL_005e
 } // end .try
 finally
 {
  IL_004f: ldloca.s CSCODE_REPLACEMENT 000
  IL_0051: constrained. valuetype [mscorlib]System.Collections.
  Generic.List`1/Enumerator
  IL_0057: callvirt instance void [mscorlib]System.
  IDisposable::Dispose()
  IL_005c: nop
  IL_005d: endfinally
 } // end handler
 IL_005e: nop
 IL_005f: ret
} // end of method Program::AnonMethod

  這裏我們能看到,實際上匿名方法和lambda表達式生成了相同的中間代碼,並且他們的執行也是類似的。

  多參數的Lambda表達式

  Lambda表達式能帶上多個參數,比如你能聲明一個Dictionary類型:

Clothing Type Count
Shirts 15
Jeans 12
Shoes 9
Pajamas 9

  如果你有一個匿名方法(FilterBy)來通過鍵和值來過濾字典,按麼你能傳遞多個參數給lambda表達式來調用這個匿名方法。附帶的代碼完成了這個FilterBy的功能:

var ClothesListShortage = clothesList.FilterBy((string name, int count)
=> name == "Shoes" && count < 10);
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章