1. 程式人生 > 實用技巧 >springboot專案基於jenkins、docker自動構建釋出

springboot專案基於jenkins、docker自動構建釋出

Lambda表示式

Lambada表示式是一種可以替代委託例項的匿名方法。編譯器會立即將Lambda表示式轉換為一下兩種形式之一:

  • 一個委託例項
  • 一個型別為Expression的表示式樹(這個後面將)

匿名方法

上面說Lambada是一種匿名方法,那麼就要先了解一下什麼是匿名方法

匿名方法是C#2.0引入的特性

匿名方法的寫法實在delegate關鍵字後面跟上引數的宣告(可選),然後是方法體

using System;
class Program
{
    delegate void Example();
    static void Main(string[] args)
    {
        // Example e = delegate(){ Console.WriteLine("一個匿名方法的實現"); };
        // 如果沒有引數,可以省略引數的括號
        Example e = delegate { Console.WriteLine("一個匿名方法的實現"); };
        e();
    }
}

這裡聲明瞭一個匿名方法,匿名方法解決的問題,是有時候想用委託,就必須需要一個方法,但是這個方法只是在這個委託中使用一下子,不需要在其他地方複用,於是引入了匿名方法

delegate(){ Console.WriteLine("一個匿名方法的實現"); };

匿名方法的寫法其實與普通方法並無異樣,只是用delegate關鍵字在前標註,省略掉方法名(如果不好理解,可以理解成方法名為delegate,沒有引數可以省略括號,只能用於註冊進委託)

匿名方法使用情況不多,因為C#3.0引入的Lambda更加強大,也是後面要講的重點

匿名方法目前最廣泛的用法,是用於宣告空事件處理器的事件

public event EventHandler Clicked =delegate {  };

Clicked事件不會進行任何操作,因為沒有定義任何操作,但是Clicked不為空,不會拋異常,在使用者層面,就是點選了某個按鈕後沒有任何變化,但是如果Clicked為空,就會拋異常

完全省略引數的宣告是匿名方法獨有的特性,即使委託需要這些引數宣告,如上面宣告空事件處理器的事件,EventHandler其實需要一個object的引數和一個EventArgs型別的引數

Lambda表示式

Lambda是一種更強大匿名方法,前面講了匿名方法,先來看看Lambda如何替代匿名方法

using System;
class Program
{
    delegate void Example();
    static void Main(string[] args)
    {
        // Example e = () => { Console.WriteLine("一個Lambda表示式"); };
        // 當方法體只有一句時可以省略大括號
        Example e = () =>  Console.WriteLine("一個Lambda表示式"); 
        e();
    }
}

從程式碼中可以看到,匿名方法被替換成了這樣一句

() => { Console.WriteLine("一個Lambda表示式"); }

在Lambda表示式中=>之前的是方法的引數,=>之後是方法體

引數和方法體的編寫規則
  • 編譯器通常可以根據上下文推斷出Lambda表示式的型別,但是當無法推斷的時候則必須顯式指定每一個引數的型別
// 能夠推斷引數型別
(x) => { return x; }
// 不能推斷引數型別
(int x) => { return x;}
  • 沒有引數,一個引數和多個引數時的寫法
// 沒有引數時小括號不能省略
() => { Console.WriteLine("一個Lambda表示式"); }

(x) => { return x; }
// 只有一個引數時可以省略小括號
x => { return x; }

// 多個引數時小括號不能省略
(x,y,z) => { return x+y+z; }
  • 方法體只有一條語句的時候,可以省略大括號,return也可以省略;方法體有多條語句時大括號不能省略
x => { return x; }
// 一條語句可以省略大括號
x => return x;
// 一條語句可以省略return
x => x;

// 多條語句時不能省略大括號和return
x =>
{
    Console.WriteLine("看看");
    return x;
}; 

Lambda表示式的閉包和foreach

Lambda表示式可以引用方法內定義的區域性變數和方法的引數(外部變數)

Lambda表示式所引用的外部變數稱為捕獲變數,捕獲變數的表示式稱為閉包

using System;
class Program
{
    static void Main(string[] args)
    {
        int x = 2;
        Func<int, int> sum = n => n + x;
        Console.WriteLine(sum(10));    // 輸出12
    }
}

在這個例子中,x就是被捕獲的變數

捕獲變數的值

Lambda表示式捕獲的變數是在呼叫委託時賦值,而不是在捕獲時賦值

using System;
class Program
{
    static void Main(string[] args)
    {
        int x = 2;
        // 捕獲外部變數x,但此時並沒有賦值
        Func<int, int> sum = n => n + x;
        x = 10;
        // 呼叫個委託時才賦值,此時x是10
        Console.WriteLine(sum(10));    // 輸出20
    }
}

捕獲變數的生命週期會延伸到和委託的生命週期一致

Lambda表示式foreach的兩個版本

如果Lambda捕獲迭代變數,最後會有怎樣的結果

using System;
class Program
{
    static void Main(string[] args)
    {
        Action[] actions = new Action[3];
        for (int i = 0; i < 3; i++)
        {
            actions[i] = () => Console.WriteLine(i);
        }

        foreach (var a in actions)
        {
            a();
        }
        // 輸出333
    }
}

先來看這個例子,利用Lambda表示式捕獲了for迴圈的i變數,但此時i的值並沒有確定,前面說過,Lambda捕獲的變數在呼叫時才賦值,所以這雖然捕獲了三次i,但這三次都是捕獲的同一個i,所以最後在呼叫時賦值了i的最後的值3(前面說過,捕獲變數的生命週期會延伸到和委託的生命週期一致,雖然for迴圈結束了,但是因為Lambda的捕獲延長了生命週期,3這個值保留了下來),所以最後輸出的是333

如果要解決這個問題,只需要將迴圈變數指定到內部的變數中,即

using System;
class Program
{
    static void Main(string[] args)
    {
        Action[] actions = new Action[3];
        for (int i = 0; i < 3; i++)
        {
            int temp = i;
            actions[i] = () => Console.WriteLine(temp);
        }

        foreach (var a in actions)
        {
            a();
        }
        // 輸出012
    }
}

對於lambda表示式來說,捕獲了三次temp,但是每一次都是新定義的temp,所以不受影響

下面來看一個foreach的“BUG"

using System;

class Program
{
    static void Main(string[] args)
    {
        Action[] actions = new Action[3];
        int i = 0;
        foreach (char c in "abc")
        {
            actions[i++] = () => Console.WriteLine(c);
        }

        foreach (Action action in actions)
        {
            action();
        }
        // 輸出abc
    }
}

這裡輸出結果是abc,這是因為foreach的每一個迭代變數都是不可變的,所以可以理解為循壞體中的區域性變數,也就是類似於上面的temp,但是,在C#5.0之前,結果並不是這樣的,foreach會像前面的for語言一樣解析,如果遇到老版本的程式碼,一定要特別注意