天天看點

Lambda 之一 基礎篇(MSDN)



Lambda 表達式是一種可用于建立

​​委托​​或

​​表達式目錄樹​​類型的

​​匿名函數​​。

通過使用 lambda 表達式,可以寫入可作為參數傳遞或作為函數調用值傳回的本地函數。

Lambda 表達式對于編寫 LINQ 查詢表達式特别有用。

若要建立 Lambda 表達式,需要在 Lambda 運算符 ​​ =>​​ 左側指定輸入參數(如果有),然後在另一側輸入表達式或語句塊。 例如,lambda 表達式x => x * x 指定名為x 的參數并傳回 x 如下面的示例所示,你可以将此表達式配置設定給委托類型:

C#

delegate int del(int i);
static void Main(string[] args)
{
    del myDelegate = x => x * x;
    int j = myDelegate(5); //j = 25      

若要建立表達式目錄樹類型:

C#

using System.Linq.Expressions;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<del> myET = x => x * x;
        }
    }
}      

=> 運算符具有與指派運算符 (=) 相同的優先級并且是​​右結合運算​​(參見“運算符”文章的“結合性”部分)。

Lambda 在基于方法的 LINQ 查詢中用作标準查詢運算符方法(如 ​​Where​​)的參數。

使用基于方法的文法在 ​​Enumerable​​​ 類中調用​​Where​​​ 方法時(如在 LINQ to Objects 和 LINQ to XML 中一樣),參數是委托類型​​System.Func<T, TResult>​​​。使用 Lambda 表達式建立該委托最為友善。 例如,當你在 ​​System.Linq.Queryable​​​ 類中調用相同的方法時(如在 LINQ to SQL 中一樣),參數類型為​​System.Linq.Expressions.Expression​​<Func>,其中 Func 是最多具有十六個輸入參數的任何一個 Func 委托。同樣,Lambda 表達式隻是一種非常簡潔的構造該表達式目錄樹的方式。盡管事實上通過 Lambda 建立的對象具有不同的類型,但 Lambda 使得 Where 調用看起來類似。

在上一個示例中,請注意委托簽名具有一個 int 類型的隐式類型輸入參數,并傳回int。可以将 Lambda 表達式轉換為該類型的委托,因為該表達式也具有一個輸入參數 (x),以及一個編譯器可隐式轉換為int

在 ​​ is​​​ 或 ​​ as​​ 運算符的左側不允許使用 Lambda。

适用于匿名方法的所有限制也适用于 Lambda 表達式。 有關更多資訊,請參見​​匿名方法(C# 程式設計指南)​​。

表達式 lambda

表達式位于 => 運算符右側的 lambda 表達式稱為“表達式 lambda”。 表達式 lambda 廣泛用于​​表達式樹(C# 和 Visual Basic)​​的構造。表達式 lambda 會傳回表達式的結果,并采用以下基本形式:

(input parameters) => expression

僅當 lambda 隻有一個輸入參數時,括号才是可選的;否則括号是必需的。 括号内的兩個或更多輸入參數使用逗号加以分隔:

C#

(x, y) => x == y

有時,編譯器難以或無法推斷輸入類型。 如果出現這種情況,你可以按以下示例中所示方式顯式指定類型:

C#

(int x, string      

使用空括号指定零個輸入參數:

C#

() => SomeMethod()

在上一個示例中,請注意表達式 Lambda 的主體可以包含一個方法調用。 但是,如果要建立在 .NET Framework 之外計算的表達式目錄樹(例如,在 SQL Server 中),則不應在 lambda 表達式中使用方法調用。在 .NET 公共語言運作時上下文之外,方法将沒有任何意義。

語句 lambda

語句 lambda 與表達式 lambda 表達式類似,隻是語句括在大括号中:

​​複制​​

(input parameters) => {statement;}

語句 lambda 的主體可以包含任意數量的語句;但是,實際上通常不會多于兩個或三個。

C#

delegate void TestDelegate(string s);
…
TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };
myDel("Hello");      

像匿名方法一樣,語句 lambda 也不能用于建立表達式目錄樹。

異步 lambda

通過使用 ​​ async​​​ 和 ​​ await​​ 關鍵字,你可以輕松建立包含異步處理的 lambda 表達式和語句。 例如,下面的 Windows 窗體示例包含一個調用和等待異步方法ExampleMethodAsync

C#

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

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

    async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await      

你可以使用異步 lambda 添加同一事件處理程式。 async

C#

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

    async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await      

有關如何建立和使用異步方法的更多資訊,請參見​​使用 Async 和 Await 的異步程式設計(C# 和 Visual Basic)​​。

帶有标準查詢運算符的 lambda

許多标準查詢運算符都具有輸入參數,其類型是泛型委托系列 ​​Func<T, TResult>​​ 中的一種。這些委托使用類型參數來定義輸入參數的數量和類型,以及委托的傳回類型。Func 委托對于封裝使用者定義的表達式非常有用,這些表達式将應用于一組源資料中的每個元素。例如,請考慮以下委托類型:

C#

public delegate      

可以将委托執行個體化為 Func<int,bool> myFunc,其中int 是輸入參數,bool傳回值始終在最後一個類型參數中指定。Func<int, string, bool> 定義包含兩個輸入參數(int 和string)且傳回類型為bool當調用下面的Func 委托時,該委托将傳回 true 或 false 以訓示輸入參數是否等于 5:

C#

Func<int, bool> myFunc = x => x == 5;
bool result = myFunc(4); // returns false of course      

當參數類型為 Expression<Func> 時,你也可以提供 Lambda 表達式,例如在 System.Linq.Queryable 内定義的标準查詢運算符中。如果指定Expression<Func> 參數,lambda 将編譯為表達式目錄樹。

此處顯示了一個标準查詢運算符,​​Count​​ 方法:

C#

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int      

編譯器可以推斷輸入參數的類型,或者你也可以顯式指定該類型。 n)。

下面一行代碼将生成一個序列,其中包含 numbers

C#

var      

此示例展示了如何通過将輸入參數括在括号中來指定多個輸入參數。 該方法将傳回數字數組中的所有元素,直至遇到一個值小于其位置的數字為止。 不要将 lambda 運算符 (=>) 與大于等于運算符 (>=) 混淆。

C#

var      

Lambda 中的類型推理

在編寫 lambda 時,通常不必為輸入參數指定類型,因為編譯器可以根據 lambda 主體、參數的委托類型以及 C# 語言規範中描述的其他因素來推斷類型。對于大多數标準查詢運算符,第一個輸入是源序列中的元素類型。是以,如果要查詢 IEnumerable<Customer>,則輸入變量将被推斷為Customer

C#

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

Lambda 的一般規則如下:

  • Lambda 包含的參數數量必須與委托類型包含的參數數量相同。
  • Lambda 中的每個輸入參數必須都能夠隐式轉換為其對應的委托參數。
  • Lambda 的傳回值(如果有)必須能夠隐式轉換為委托的傳回類型。

請注意,lambda 表達式本身沒有類型,因為正常類型系統沒有“Lambda 表達式”這一内部概念。但是,有時以一種非正式的方式談論 lambda 表達式的“類型”會很友善。在這些情況下,類型是指委托類型或 lambda 表達式所轉換到的​​Expression​​ 類型。

Lambda 表達式中的變量範圍

在定義 lambda 函數的方法内或包含 lambda 表達式的類型内,lambda 可以引用範圍内的外部變量(參見​​匿名方法(C# 程式設計指南)​​)。以這種方式捕獲的變量将進行存儲以備在 lambda 表達式中使用,即使在其他情況下,這些變量将超出範圍并進行垃圾回收。必須明确地配置設定外部變量,然後才能在 lambda 表達式中使用該變量。下面的示例示範這些規則:

C#

delegate bool D();
delegate bool D2(int i);

class Test
{
    D del;
    D2 del2;
    public void TestMethod(int input)
    {
        int j = 0;
        // Initialize the delegates with lambda expressions.
        // Note access to 2 outer variables.
        // del will be invoked within this method.
        del = () => { j = 10;  return j > input; };

        // del2 will be invoked after TestMethod goes out of scope.
        del2 = (x) => {return x == j; };
      
        // Demonstrate value of j:
        // Output: j = 0 
        // The delegate has not been invoked yet.
        Console.WriteLine("j = {0}", j);        // Invoke the delegate.
        bool boolResult = del();

        // Output: j = 10 b = True
        Console.WriteLine("j = {0}. b = {1}", j, boolResult);
    }

    static void Main()
    {
        Test test = new Test();
        test.TestMethod(5);

        // Prove that del2 still has a copy of
        // local variable j from TestMethod.
        bool result = test.del2(10);

        // Output: True      

下列規則适用于 lambda 表達式中的變量範圍:

  • 捕獲的變量将不會被作為垃圾回收,直至引用變量的委托符合垃圾回收的條件。
  • 在外部方法中看不到 lambda 表達式内引入的變量。
  • Lambda 表達式無法從封閉方法中直接捕獲 ref 或out
  • Lambda 表達式中的傳回語句不會導緻封閉方法傳回。
  • 如果跳轉語句的目标在塊外部,則 lambda 表達式不能包含位于 lambda 函數内部的goto 語句、break 語句或continue同樣,如果目标在塊内部,則在 lambda 函數塊外部使用跳轉語句也是錯誤的。

--轉的另一篇介紹lambda的blog--

lambda簡介

lambda運算符:所有的lambda表達式都是用新的lambda運算符 " => ",可以叫他,“轉到”或者 “成為”。運算符将表達式分為兩部分,左邊指定輸入參數,右邊是lambda的主體。

        lambda表達式:

               1.一個參數:param=>expr

               2.多個參數:(param-list)=>expr

        上面這些東西,記着,下面我們開始應用并闡述lambda,讓你樂在其中。

lambda應用闡述    

         闡述這技術,我先上一個例子,然後再慢慢深入分析。例子如下:         

namespace 闡述lambda
{
    public class Person
    {
        public string Name { get; set; }
        public int Age  {  get;set; }    
    }
    class Program
    {

        public static List<Person> PersonsList()
        {
            List<Person> persons = new List<Person>();
            for (int i = 0; i < 7; i++)
            {
                Person p = new Person() { Name = i + "兒子", Age = 8 - i, };
                persons.Add(p);                
            }
            return persons;
        }

        static void Main(string[] args)
        {
            List<Person> persons = PersonsList();
            persons = persons.Where(p => p.Age > 6).ToList();       //所有Age>6的Person的集合
            Person per = persons.SingleOrDefault(p => p.Age == 1);  //Age=1的單個people類
            persons = persons.Where(p => p.Name.Contains("兒子")).ToList();   //所有Name包含兒子的Person的集合
        }
    }
}      

     看了上面的例子,相信你能看出它确實是個甜棗,呵呵,下面我們來看下(p=>p.Age>6)這樣的表達式,到底是怎麼回事。。

     首先我們看下委托  

//委托  逛超市
        delegate int GuangChaoshi(int a);
        static void Main(string[] args)
        {
            GuangChaoshi gwl = JieZhang;
            Console.WriteLine(gwl(10) + "");   //列印20,委托的應用
            Console.ReadKey();
        }
        
        //結賬
        public static int JieZhang(int a)
        {
            return a + 10;
        }      

    再看表達式

//委托  逛超市
        delegate int GuangChaoshi(int a);
        static void Main(string[] args)
        {          
           // GuangChaoshi gwl = JieZhang;
            GuangChaoshi gwl = p => p + 10;
            Console.WriteLine(gwl(10) + "");   //列印20,表達式的應用
            Console.ReadKey();
        }      

     委托跟表達式的兩段代碼,我們可以看出一些東東吧:其實表達式(p => p + 10;)中的 p 就代表委托方法中的參數,而表達式符号右邊的 p+10,就是委托方法中的傳回結果。 大俠繞道,小蝦了解下。

    下面再上兩個稍微複雜點的了解了解。

    1.多參數的

//委托  逛超市
        delegate int GuangChaoshi(int a,int b);
        static void Main(string[] args)
        {            
            GuangChaoshi gwl = (p,z) => z-(p + 10);
            Console.WriteLine(gwl(10,100) + "");   //列印80,z對應參數b,p對應參數a
            Console.ReadKey();
        }      

        2. lambda主體運算複雜  

/// <summary>
        /// 委托  逛超市
        /// </summary>
        /// <param name="a">花費</param>
        /// <param name="b">付錢</param>
        /// <returns>找零</returns>
        delegate int GuangChaoshi(int a,int b);
        static void Main(string[] args)
        {
            GuangChaoshi gwl = (p, z) =>
            {
                int zuidixiaofei = 10;
                if (p < zuidixiaofei)
                {
                    return 100;
                }
                else
                {
                    return z - p - 10;
                }
           
            };
            Console.WriteLine(gwl(10,100) + "");   //列印80,z對應參數b,p對應參數a
            Console.ReadKey();
        }      

上面這些例子,好好了解下,下面我要介紹一個系統指定的 Fun<T>委托。

Func委托

 T 是參數類型,這是一個泛型類型的委托,用起來很友善的。

 先上例子

static void Main(string[] args)
        {
            Func<int, string> gwl = p => p + 10 + "--傳回類型為string";            
            Console.WriteLine(gwl(10) + "");   //列印‘20--傳回類型為string’,z對應參數b,p對應參數a
            Console.ReadKey();
        }      

說明:我們可以看到,這裡的p為int 類型參數, 然而lambda主體傳回的是string類型的。

再上一個例子

static void Main(string[] args)
        {
            Func<int, int, bool> gwl = (p, j) =>
                {
                    if (p + j == 10)
                    {
                        return true;
                    }
                    return false;
                };
            Console.WriteLine(gwl(5,5) + "");   //列印‘True’,z對應參數b,p對應參數a
            Console.ReadKey();
        }      

說明:從這個例子,我們能看到,p為int類型,j為int類型,傳回值為bool類型。

看完上面兩個例子,相信大家應該明白啦Func<T>的用法:多個參數,前面的為委托方法的參數,最後一個參數,為委托方法的傳回類型。

lambda表達式樹動态建立方法  

static void Main(string[] args)
        {
            //i*j+w*x
            ParameterExpression a = Expression.Parameter(typeof(int),"i");   //建立一個表達式樹中的參數,作為一個節點,這裡是最下層的節點
            ParameterExpression b = Expression.Parameter(typeof(int),"j");
            BinaryExpression be = Expression.Multiply(a,b);    //這裡i*j,生成表達式樹中的一個節點,比上面節點高一級

            ParameterExpression c = Expression.Parameter(typeof(int), "w");
            ParameterExpression d = Expression.Parameter(typeof(int), "x");
            BinaryExpression be1 = Expression.Multiply(c, d);

            BinaryExpression su = Expression.Add(be,be1);   //運算兩個中級節點,産生終結點

            Expression<Func<int, int, int, int, int>> lambda = Expression.Lambda<Func<int, int, int, int, int>>(su,a,b,c,d);

            Console.WriteLine(lambda + "");   //列印‘(i,j,w,x)=>((i*j)+(w*x))’,z對應參數b,p對應參數a

            Func<int, int, int, int, int> f= lambda.Compile();  //将表達式樹描述的lambda表達式,編譯為可執行代碼,并生成該lambda表達式的委托;

            Console.WriteLine(f(1, 1, 1, 1) + "");  //列印2
            Console.ReadKey();
        }      

繼續閱讀