歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> C#匿名方法中的變量

C#匿名方法中的變量

日期:2017/3/1 9:32:02   编辑:Linux編程

前面一篇文章看到了C# 2.0中通過匿名方法來簡化委托(見 http://www.linuxidc.com/Linux/2015-02/114153.htm),下面來看看匿名方法中的變量。

閉包和不同的變量類型

閉包的基本概念是:一個函數除了能夠通過提供給它的參數與環境交互之外,還能同環境進行更大程度的互動。對於C# 2.0中出現的匿名方法的閉包表現為,匿名方法能使用在聲明該匿名方法的方法內部定義的局部變量

在進一步了解閉包之前,我們先看看下面兩個術語:

外部變量(outer variable):是指其作用域(scope)包括一個匿名方法的局部變量或參數(ref和out參數除外)

被捕捉的外部變量(captured outer variable):它是在匿名方法內部使用的外部變量

結合上面的解釋,來看一個被捕獲的變量的例子:

private static void EnclosingMethod()
{
    //未被捕獲的外部變量
    int outerVariable = 2;
    //被匿名方法捕獲的外部變量
    string capturedVariable = "captured variable";

    if (DateTime.Now.Hour == 23)
    {
        //普通局部變量
        int normalLocalVarialbe = 3;
        Console.WriteLine(normalLocalVarialbe);
    }

    Action x = delegate
    {
        //匿名方法的局部變量
        string anonymousLocal = "local variable of anonymous method";
        //獲得被捕獲的外部變量
        Console.WriteLine(capturedVariable);
        Console.WriteLine(anonymousLocal);
    };
    x();
}

一個變量被捕獲之後,被匿名方法捕獲的是這個變量,為不是創建委托實例時該變量的值。下面通過一個例子來看看這句描述。

private static void CapturedVariableTesting()
{
    string captured = "before x is created";

    Action x = delegate
    {
        Console.WriteLine(captured);
        captured = "changed by x";
    };

    captured = "changed before x is invoked";
    x();

    Console.WriteLine(captured);

    captured = "before second invocation";
    x();
}

代碼的輸出為:

在CapturedVariableTesting這個方法中,我們始終都是在使用同一個被捕獲變量captured;也就是說,在匿名方法外對被捕獲變量的修改,在匿名方法內部是可見的,反之亦然。

捕捉變量的用途

閉包的出現給我們帶來很多的便利,直接利用被捕獲變量可以簡化編程,避免專門創建一些類來存儲一個委托需要處理的信息。

看一個例子,我們給定一個上限,來獲取List中所有小於這個上限的數字。

private static List<int> FindAllLessThan(List<int> numList, int upperLimitation)
{
    return numList.FindAll(delegate(int num)
    {
        return num < upperLimitation;
    });
}

由於閉包的出現,我們不用將upperLimitation這個變量以函數參數的形式傳給匿名函數,在匿名方法中可以直接使用這個被捕獲的變量。

捕獲變量的工作原理

前面看到的例子都比較簡單,下面我們看一個稍微復雜的例子:

static void Main(string[] args)
{
    Action x = CreateDelegateInstance();
    x();
    x();
    Console.Read();
}

private static Action CreateDelegateInstance()
{
    int counter = 5;
    Action ret = delegate 
    {
        Console.WriteLine(counter);
        counter++;
    };

    ret();
    return ret;
}

代碼輸出為:

為什麼結果是5,6,7?變量counter在CreateDelegateInstance方法結束後為什麼沒有被銷毀?

當我們查看這個例子的IL代碼時,發現編譯器為我們創建了一個類"<>c__DisplayClass1"。

.class nested private auto ansi sealed beforefieldinit '<>c__DisplayClass1'
    extends [mscorlib]System.Object
{
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
        01 00 00 00
    )
    // Fields
    .field public int32 counter

    // Methods
    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x2078
        // Code size 7 (0x7)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: call instance void [mscorlib]System.Object::.ctor()
        IL_0006: ret
    } // end of method '<>c__DisplayClass1'::.ctor

    .method public hidebysig 
        instance void '<CreateDelegateInstance>b__0' () cil managed 
    {
        // Method begins at RVA 0x2080
        // Code size 28 (0x1c)
        .maxstack 8

        IL_0000: nop
        IL_0001: ldarg.0
        IL_0002: ldfld int32 AnonymousMethod.Program/'<>c__DisplayClass1'::counter
        IL_0007: call void [mscorlib]System.Console::WriteLine(int32)
        IL_000c: nop
        IL_000d: ldarg.0
        IL_000e: dup
        IL_000f: ldfld int32 AnonymousMethod.Program/'<>c__DisplayClass1'::counter
        IL_0014: ldc.i4.1
        IL_0015: add
        IL_0016: stfld int32 AnonymousMethod.Program/'<>c__DisplayClass1'::counter
        IL_001b: ret
    } // end of method '<>c__DisplayClass1'::'<CreateDelegateInstance>b__0'

} // end of class <>c__DisplayClass1

而在CreateDelegateInstance方法的IL代碼中可以看到,CreateDelegateInstance的局部變量counter實際上就是"<>c__DisplayClass1"對象的counter字段

IL_0000: newobj instance void AnonymousMethod.Program/'<>c__DisplayClass1'::.ctor()
IL_0005: stloc.1
IL_0006: nop
IL_0007: ldloc.1
IL_0008: ldc.i4.5
IL_0009: stfld int32 AnonymousMethod.Program/'<>c__DisplayClass1'::counter

通過上面的分析可以看到,編譯器創建了一個額外的類來容納變量,CreateDelegateInstance方法擁有該類的一個實例引用,並通過這個引用訪問counter變量。counter這個局部變量並不是在"調用棧"空間上,這也就解釋了為什麼函數返回後,這個變量沒有被銷毀。

在上面的例子中只有一個委托實例,下面再看一個擁有多個委托實例的例子:

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

    for(int index = 0; index < 5; index++)
    {
        int counter = index * 10;
        list.Add(delegate
        {
            Console.WriteLine(counter);
            counter++;
        });
    }

    foreach (Action x in list)
    {
        x();
    }

    list[0]();
    list[0]();

    list[1]();

    Console.Read();
}

代碼輸出為:

通過輸出可以看到,每個委托實例將捕獲不同的變量。

所以被捕獲變量的聲明期可以總結為:對於一個被捕獲的變量,只要還有任何委托實例在引用它,它就會一直存在;當一個變量被捕獲時,捕獲的是變量的"實例"。

總結

本文介紹了閉包和不同的變量類型。在匿名方法中,通過被捕獲變量,我們可以使用"現有"的上下文信息,而不必專門設置額外的類型來存儲一些已知的數據。

同時,介紹了被捕獲變量的生命期,通過IL代碼看到了被捕獲變量的工作原理。

Copyright © Linux教程網 All Rights Reserved