擼·委託

C# 中的委託(Delegate)相似於 C 或 C++ 中函數的指針。委託(Delegate) 是存有對某個方法的引用的一種引用類型變量,引用可在運行時被改變。 委託(Delegate)特別用於實現事件和回調方法,全部的委託(Delegate)都派生自 System.Delegate 類。
  委託是尋址方法的.NET版本,在C++中,函數指針只不過是一個指向內存位置的指針,它不是類型安全.咱們沒法判斷這個指針實際指向什麼,像參數和返回類型等項就更無從知曉了。而.NET委託徹底不一樣,委託是類型安全的,它定義了返回類型和參數的類型.委託類不只包含對方法的引用,也能夠包含對多個方法的引用。

Lambda表達式與委託直接相關,當參數是委託類型時,就可使用Lambda表達式引用方法.sql

委託的本質 是class,能夠查看IL代碼數據庫

爲何要用委託

  1. 能夠將方法當作另外一個方法的參數來進行傳遞,使得程序更容易擴展(好比策略模式--封裝變化的部分,全部封裝變化的代碼都會減小編寫代碼的工做量)。
  2. 把過程的調用轉化爲對象的調用,充分體現了委託增強了面向對象編程的思想。
  3. 使得方法聲明和方法實現的分離,充分體現了面向對象的編程思想。

委託聲明

委託聲明決定了可由該委託引用的方法,委託可指向一個與其具備相同標籤的方法。
例如,假設有一個委託:express

public delegate int MyDelegate (string s);

理解委託,須要理解如下幾點:編程

  1. 理解委託的一個要點就是它的類型安全很是高,在定義委託時,必須給出它所表示的方法的簽名和返回類型等所有細節。
  2. 理解委託的一種好方式是把委託看成這樣一件事,它給方法的簽名和返回類型指定名稱。
  3. 其語法相似於方法的定義,但沒有方法體,定義的前面要加上關鍵字delegate。由於定義委託基本是定義一個新類,因此能夠在定義類的任何相同地方定義委託。
  4. 也就是說,能夠在另外一個類的內部定義,也能夠在任何類的外部定義,還能夠在名稱空間中把委託定義爲頂層對象。
  5. 根據定義的可見性,和委託的做用域,能夠在委託的定義上應用任意常見的訪問修飾符:pubic,private,protected等。

實例化委託:
  給委託變量賦值時,千萬不要將方法帶括號,帶括號意味賦值是方法的結果,因此直接賦值方法名。c#

系統預約委託

Action

Action:它表示引用一個void返回類型的方法。這個委託類存在不一樣的變體,能夠傳遞至多16種不一樣的參數類型數組

  • Acition 屬於無參無返回值的函數類型
  • Action< T>經過設置泛型,咱們能夠定義有多個參數, 無返回值的函數
  • 當函數有多個重載的時候,系統會自動匹配
  • Action是沒有返回值的
  • 參數也是0或者最多16個
Action 說明
Action 調用沒有參數的方法
Action<in T1,> 調用帶一個參數的方法, T1後面不帶逗號,由於網頁顯示不出來,因此加上去
Action<in T1,in T2> 調用帶兩個參數的方法,
Action<in T1,in T2,in T3> 調用了帶3個參數的方法
...... 直到調用了帶16個參數的方法

Func

Func:它表示引用一個必有返回類型的方法。這個委託類存在不一樣的變體,能夠傳遞至多16種不一樣的參數類型,但必須有一個返回值。安全

  • Func只有帶泛型的一種形式,Action有帶泛型和不帶的兩種
  • Func 委託必需要帶有一個返回值
  • 能夠有0個或多達16個參數類型
  • 最後一個泛型參數表明返回類型,前面的都是參數類型
  • 參數類型必須跟指向的方法的參數類型按照順序對應
Func 說明
Func<out TResult,> 調用沒有參數的方法,可是有返回值,TResult後面不帶逗號,由於網頁顯示不出來,因此加上去
Action<in T,out TResult > 調用帶一個參數的方法,返回一個值
Action<in T1,in T2,out TResult> 調用帶兩個參數的方法,返回一個值
Action<in T1,in T2,in T3,out TResult> 調用了帶3個參數的方法,返回一個值
...... 直到調用了帶16個參數的方法

Predicate

Predicate:表示定義一組條件並肯定指定對象是否符合這些條件的方法。
此委託由 Array 和 List 類的幾種方法使用,用於在集合中搜索元素。數據結構

public delegate bool Predicate<in T>(T obj);

類型參數:T
要比較的對象的類型。參數:obj 。要按照由此委託表示的方法中定義的條件進行比較的對象。
返回值:System.Boolean。若是 obj 符合由此委託表示的方法中定義的條件,則爲 true;不然爲 false。
繼承:ObjectDelegatePredicate
示例:
多線程

using System;
using System.Drawing;

public class Example
{
   public static void Main()
   {
      // Create an array of Point structures.
      Point[] points = { new Point(100, 200), 
                         new Point(150, 250), new Point(250, 375), 
                         new Point(275, 395), new Point(295, 450) };

      // Define the Predicate<T> delegate.
      Predicate<Point> predicate = FindPoints;
      
      // Find the first Point structure for which X times Y  
      // is greater than 100000. 
      Point first = Array.Find(points, predicate);

      // Display the first structure found.
      Console.WriteLine("Found: X = {0}, Y = {1}", first.X, first.Y);
   }

   private static bool FindPoints(Point obj)
   {
      return obj.X * obj.Y > 100000;
   }
}
// The example displays the following output:
//        Found: X = 275, Y = 395

下面的示例等同於上一示例中,只不過它使用 lambda 表達式來表示Predicate 委託。 每一個元素points數組傳遞給 lambda 表達式,該表達式查找符合搜索條件的元素以前。 在這種狀況下,lambda 表達式返回true是否大於 100,000 的 X 和 Y 字段。 閉包

using System;
using System.Drawing;

public class Example
{
   public static void Main()
   {
      // Create an array of Point structures.
      Point[] points = { new Point(100, 200), 
                         new Point(150, 250), new Point(250, 375), 
                         new Point(275, 395), new Point(295, 450) };

      // Find the first Point structure for which X times Y  
      // is greater than 100000. 
      Point first = Array.Find(points, x => x.X * x.Y > 100000 );

      // Display the first structure found.
      Console.WriteLine("Found: X = {0}, Y = {1}", first.X, first.Y);
   }
}
// The example displays the following output:
//        Found: X = 275, Y = 395

多播委託

  若是要調用多個方法,就須要屢次顯式調用這個委託。可是委託也能夠包含多個方法,這種委託稱爲多播委託。
若是調用多播委託就能夠按順序連續調用多個方法。爲此,委託的簽名就必須返回void;不然,就只能獲得委託調用的最後一個方法的結果。
例如:

  • Action operations=方法一;operartion+=方法二;
  • 也能夠 委託1=方法1;委託二=方法2;委託3=委託1+委託2。

因此當調用委託3時,委託1和委託2會同時執行,與第一種實現多播委託一個道理。
可是多播委託還有一個缺陷,一旦一個委託發生異常,其餘委託都會中止。爲了不這個問題,應本身迭代方法列表。Delegate類定義GetInvocationList()方法,它返回一個Delegate對象數組。如今可使用這個委託調用與委託直接相關的方法,捕獲異常,並繼續下一次迭代。(.net core 中間件

匿名方法

  到目前爲止,要想使委託工做,方法名必須已經存在。但還有另一種使用委託的方式:匿名方法。匿名方法是用做委託的參數的一段代碼,用匿名方法定義委託的語法與前面的定義並無區別,但在實例化委託時,就有了區別。下面是一個很是簡單的代碼,它說明了如何使用匿名方法:

Func<string,string> anonDel=delegate(string param)
{
param+=mid;
param+=" and this was added to the string.";
return param;
}

使用匿名方法的規則很明顯,它前面是關鍵字delegate,後面是一個字符串參數.

使用匿名方法

  1. 聲明委託變量時候做爲初始化表達式。
  2. 組合委託時在賦值語句的右邊。
  3. 爲委託增長事件時在賦值語句的右邊。

匿名方法語法

delegate (parameters ){implementationcode};
關鍵字  參數        方法體

匿名方法不會聲明返回值類型。可是匿名方法返回值類型必須和委託返回值同樣。

進階

咱們可使圓括號爲空,或省略圓括號來簡化匿名方法的參數列表。可是僅在下面兩項都爲真的狀況下才能夠這麼作。

  1. 委託的參數列表不包含任何out參數的委託。
  2. 匿名方法不使用任何參數。
    例如:
class Program
    {
        delegate int otherdel(int param);
        public static void Main()
        {
            otherdel del = delegate
            {
                cleanup();
                printMessage();
            };          
        }
    }

params參數

若是委託參數包含params參數,那麼params關鍵字就會被匿名方法的參數列表忽略。以下:

delegate int otherdel(int x,params int y);

        otherdel del = delegate(int x,int y)
        {

        };

Lambda表達式

  從c#3.0開始,就可使用一種新語法把實現代碼賦予委託:Lambda表達式
只要有委託參數類型的地方,就可使用Lambda表達式。前面使用匿名方法的例子能夠改成使用Lambda表達式。
Lambda表達式的語法比匿名方法簡單.若是所調用的方法有參數,且不須要參數,匿名方法的語法就比較簡單,由於這樣不須要提供參數

定義:Labmda表達式有幾種定義參數的方式.

  1. 若是隻一個參數,只寫出參數名就足夠了。下面的Lambda表達式使用了參數s,由於委託類型定義了一個參數string,因此s的類型就是string。實現代碼調用String.Format()方法來返回一個字符串,在調用該委託時就把字符串寫到控制檯上:
Func<string,string> oneParam=s=>string.Format("change uppercase{0}",s.ToUpper());

Console.WriteLine(oneParam("test"));
  1. 若是使用多個參數,就把參數名放在花括號中,這裏參數x和y的類型是double,由Func<double,double,double>委託定義。例如:
Func<double,double,double> twoParams=(x,y)=>x*y;

console.WriteLine(twoParams(3,2));
  1. 固然,爲了方便,也能夠給花括號中的變量添加參數類型,這樣若是編譯器不能匹配重載後的版本,那麼使用參數類型能夠幫助找到匹配的委託:
func<double,double,double> twoParamsWithTypes=(double x,double y)=>x*y ; 

Console.Write(twoParamsWithTypes(2,3));
  1. 若是有多行代碼.就得添加花括號,return和分號了,並且這比不添加更容易閱讀
Func<double,double> square=x=>{ return x*x };

閉包

經過Lambda表達式能夠訪問表達式外部的變量,這稱爲閉包。閉包是一個很是好的功能,但若是未正確使用,也會很是危險。 多線程使用時就會出兮兮,由於不知道此時外部變量是什麼。

方法泛型集合定義:List<Func<int>>();

Expression

  lambda表達式是被視爲一個對象的代碼(表達式或語句塊)的塊。它能夠做爲參數傳遞給方法,也能夠經過方法調用返回。Lambda表達式普遍用於:

  • 將要執行的代碼傳遞給異步方法,例如Task.Run(Action)。
  • 編寫LINQ查詢表達式。
  • 建立表達式樹。

lambda表達式除了能夠分配給委託類型外,還能夠分配給表達式樹類型,如:

System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x;
Console.WriteLine(e);
// Output:
// x => (x * x)

或者能夠直接將其做爲方法參數傳遞:

int[] numbers = { 2, 3, 4, 5 };
var squaredNumbers = numbers.Select(x => x * x);
Console.WriteLine(string.Join(" ", squaredNumbers));
// Output:
// 4 9 16 25

  當您使用基於方法的語法來調用System.Linq.Enumerable類中的Enumerable.Select方法時(就像在LINQ to Objects和LINQ to XML中那樣),參數是委託類型System.Func <T,TResult>。lambda表達式是建立該委託的最便捷方式。
  當您在System.Linq.Queryable類中調用Queryable.Select方法時(就像在LINQ to SQL中那樣),參數類型是表達式樹類型。一樣,lambda表達式只是構造表達式樹的一種很是簡潔的方式。lambda容許調用看起來相似,但實際上從lambda建立的對象類型是不一樣的。Expression<Func<TSource,TResult>>Select

表達lambda

在=>運算符右側具備表達式的lambda表達式稱爲表達式lambda。表達式lambda普遍用於構建表達樹。表達式lambda返回表達式的結果,並採用如下基本形式:

(inputParameters) => expression

只有當lambda有一個輸入參數時,括號纔是可選的。不然他們是必需的。
使用空括號指定零輸入參數:

Action line = () => Console.WriteLine();

兩個或多個輸入參數用括在括號中的逗號分隔:

Func<int, int, bool> testForEquality = (x, y) => x == y;

有時,編譯器沒法推斷輸入類型。您能夠顯式指定類型,如如下示例所示:

Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;

輸入參數類型必須所有顯式或所有隱式; 不然,會發生CS0748編譯器錯誤。

表達式lambda的主體能夠包含方法調用。可是,若是要建立在.NET公共語言運行庫的上下文以外計算的表達式樹(例如在SQL Server中),則不該在lambda表達式中使用方法調用。這些方法在.NET公共語言運行庫的上下文以外沒有任何意義。

聲明lambda

語句lambda相似於表達式lambda,除了語句括在括號中:

(input-parameters) => { statement; }

語句lambda的主體能夠包含任意數量的語句; 然而,在實踐中一般不超過兩個或三個。

Action<string> greet = name => 
{ 
    string greeting = $"Hello {name}!";
    Console.WriteLine(greeting);
};
greet("World");
// Output:
// Hello World!

語句lambda與匿名方法同樣,不能用於建立表達式樹。

異步lambda

您可使用async和await關鍵字輕鬆建立包含異步處理的lambda表達式和語句。例如,如下Windows窗體示例包含一個調用和等待異步方法的事件處理程序ExampleMethodAsync。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        button1.Click += button1_Click;
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        await ExampleMethodAsync();
        textBox1.Text += "\r\nControl returned to Click event handler.\n";
    }

    private async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await Task.Delay(1000);
    }
}

可使用異步lambda添加相同的事件處理程序。要添加此處理程序,請async在lambda參數列表以前添加修飾符,如如下示例所示:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        button1.Click += async (sender, e) =>
        {
            await ExampleMethodAsync();
            textBox1.Text += "\r\nControl returned to Click event handler.\n";
        };
    }

    private async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await Task.Delay(1000);
    }
}

Lambda表達式和元組

從C#7.0開始,C#語言爲元組提供內置支持。你能夠提供一個元組做爲lambda表達式的參數,你的lambda表達式也能夠返回一個元組。在某些狀況下,C#編譯器使用類型推斷來肯定元組組件的類型。

您能夠經過在括號中包含逗號分隔的組件列表來定義元組。下面的示例使用帶有三個組件的元組將一系列數字傳遞給lambda表達式,該表達式將每一個值加倍並返回包含三個包含乘法結果的組件的元組。

Func<(int, int, int), (int, int, int)> doubleThem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");
// Output:
// The set (2, 3, 4) doubled: (4, 6, 8)

Lambda與標準查詢運算符

除了其餘實現以外,LINQ to Objects還有一個輸入參數,其類型是Func 系列泛型委託之一。這些委託使用類型參數來定義輸入參數的數量和類型,以及委託的返回類型。Func委託對於封裝應用於一組源數據中的每一個元素的用戶定義表達式很是有用。例如,考慮Func <T,TResult>委託類型:

當參數類型是Expression 時,您還能夠提供lambda表達式,例如在Queryable類型中定義的標準查詢運算符中。指定Expression 參數時,lambda將編譯爲表達式樹。
如下示例使用Count標準查詢運算符:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
Console.WriteLine($"There are {oddNumbers} odd numbers in {string.Join(" ", numbers)}");

編譯器能夠推斷輸入參數的類型,也能夠顯式指定它。這個特殊的lambda表達式計算那些整數(n),當除以2時,餘數爲1。

如下示例生成一個序列,其中包含numbers數組中位於9以前的全部元素,由於這是序列中不符合條件的第一個數字:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstNumbersLessThanSix = numbers.TakeWhile(n => n < 6);
Console.WriteLine(string.Join(" ", firstNumbersLessThanSix));
// Output:
// 5 4 1 3

在lambda表達式中鍵入推斷

編寫lambda時,一般沒必要爲輸入參數指定類型,由於編譯器能夠根據lambda正文,參數類型和C#語言規範中描述的其餘因素推斷類型。對於大多數標準查詢運算符,第一個輸入是源序列中元素的類型。若是要查詢IEnumerable ,則輸入變量被推斷爲Customer對象,這意味着您能夠訪問其方法和屬性:

customers.Where(c => c.City == "London");

lambdas類型推斷的通常規則以下:

  • lambda必須包含與委託類型相同數量的參數。
  • lambda中的每一個輸入參數必須可隱式轉換爲其對應的委託參數。
  • lambda的返回值(若是有的話)必須能夠隱式轉換爲委託的返回類型。

    請注意,lambda表達式自己沒有類型,由於公共類型系統沒有「lambda表達式」的內在概念。然而,有時很是方便地談論lambda表達式的「類型」。在這些狀況下,類型引用lambda表達式轉換爲的委託類型或表達式類型。

lambda表達式中的變量範圍

Lambda能夠引用外部變量(請參閱匿名方法),這些變量位於定義lambda表達式的方法的範圍內,或者在包含lambda表達式的類型的範圍內。以這種方式捕獲的變量被存儲用於lambda表達式,即便變量不然將超出範圍並被垃圾收集。必須明確賦值外部變量,而後才能在lambda表達式中使用它。如下示例演示瞭如下規則:

public static class VariableScopeWithLambdas
{
    public class VariableCaptureGame
    {
        internal Action<int> updateCapturedLocalVariable;
        internal Func<int, bool> isEqualToCapturedLocalVariable;

        public void Run(int input)
        {
            int j = 0;

            updateCapturedLocalVariable = x =>
            {
                j = x;
                bool result = j > input;
                Console.WriteLine($"{j} is greater than {input}: {result}");
            };

            isEqualToCapturedLocalVariable = x => x == j;

            Console.WriteLine($"Local variable before lambda invocation: {j}");
            updateCapturedLocalVariable(10);
            Console.WriteLine($"Local variable after lambda invocation: {j}");
        }
    }

    public static void Main()
    {  
        var game = new VariableCaptureGame();
        
        int gameInput = 5;
        game.Run(gameInput);

        int jTry = 10;
        bool result = game.isEqualToCapturedLocalVariable(jTry);
        Console.WriteLine($"Captured local variable is equal to {jTry}: {result}");

        int anotherJ = 3;
        game.updateCapturedLocalVariable(anotherJ);

        bool equalToAnother = game.isEqualToCapturedLocalVariable(anotherJ);
        Console.WriteLine($"Another lambda observes a new value of captured variable: {equalToAnother}");
    }
    // Output:
    // Local variable before lambda invocation: 0
    // 10 is greater than 5: True
    // Local variable after lambda invocation: 10
    // Captured local variable is equal to 10: True
    // 3 is greater than 5: False
    // Another lambda observes a new value of captured variable: True
}

如下規則適用於lambda表達式中的變量做用域:

  • 捕獲的變量將不會被垃圾收集,直到引用它的委託纔有資格進行垃圾回收。
  • lambda表達式中引入的變量在封閉方法中不可見。
  • lambda表達式不能直接從封閉方法中捕獲in,ref或out參數。
  • 一回在lambda表達式語句不會致使封閉方法返回。
    若是該跳轉語句的目標位於lambda表達式塊以外,則lambda表達式不能包含goto,break或continue語句。若是目標在塊內,則在lambda表達式塊以外有一個跳轉語句也是錯誤的。

表達式樹

定義 :將強類型lambda表達式表示爲表達式樹形式的數據結構。這個類不能被繼承。
場景: 目前被大量運行在linq to sql 中。將表達式樹轉換成表達式,而後轉換成SQL。

public sealed class Expression<TDelegate> : System.Linq.Expressions.LambdaExpression

下面的代碼示例演示如何將lambda表達式表示爲委託形式的可執行代碼和表達式樹形式的數據。它還演示瞭如何使用Compile方法將表達式樹轉換回可執行代碼。

// Lambda expression as executable code.
Func<int, bool> deleg = i => i < 5;
// Invoke the delegate and display the output.
Console.WriteLine("deleg(4) = {0}", deleg(4));

// Lambda expression as data in the form of an expression tree.
System.Linq.Expressions.Expression<Func<int, bool>> expr = i => i < 5;
// Compile the expression tree into executable code.
Func<int, bool> deleg2 = expr.Compile();
// Invoke the method and print the output.
Console.WriteLine("deleg2(4) = {0}", deleg2(4));

/*  This code produces the following output:

    deleg(4) = True
    deleg2(4) = True
*/

表達式樹是lambda表達式的內存數據表示。表達式樹使lambda表達式的結構透明和顯式。您能夠像處理任何其餘數據結構同樣與表達式樹中的數據進行交互。

將表達式視爲數據結構的能力使API可以以能夠自定義方式檢查,轉換和處理的格式接收用戶代碼。例如,LINQ to SQL數據訪問實現使用此工具將表達式樹轉換爲可由數據庫評估的Transact-SQL語句。

Queryable類中定義的許多標準查詢運算符都有一個或多個Expression 類型的參數。

所述的NodeType一個的表達 是LAMBDA。

使用Lambda (Expression,IEnumerable )或Lambda (Expression,ParameterExpression [])方法建立Expression 對象。

方法

相關文章
相關標籤/搜索