簡體   English   中英

C#閉包變量范圍

[英]C# closure variable scope

關於如何應用與閉包相關的變量范圍的問題(另一個?)。 這是一個最小的例子:

public class Foo
{
    public string name;
    public Foo(string name)
    {
        this.name = name;
    }
}


public class Program
{
    static Action getAction(Foo obj)
    {
        return () => Console.WriteLine(obj.name); 
    }

    static void Main(string[] args)
    {
        Foo obj1 = new Foo("x1");    
        Action a = getAction(obj1);  
        obj1 = new Foo("x2");        
        a();                         
    }
}

這打印x1 它可以解釋為:

getAction返回一個匿名函數,它有一個封閉變量obj的閉包。 obj具有相同的基准作為obj1但它與關系obj1結束那里作為閉合僅包圍obj 換句話說, obj1之后的任何值都不會影響閉包。 因此,每當/然而a被調用(如a被傳遞給了其他一些功能),它會永遠印x1

現在我的問題是:

  1. 以上解釋是否正確?
  2. 我沒有特定的場景,但如果我們希望程序打印x2 (例如封閉以封閉外部范圍),該怎么辦? 它可以完成(或者甚至嘗試都沒有意義)?

我們考慮一下:

static Action getAction(Foo obj)
{
    return () => Console.WriteLine(obj.name); 
}

閉包超過參數 obj ; 這個obj是一個通過值傳遞的引用,所以如果調用者執行:

x = someA();
var action = getAction(x);
x = someB(); // not seen by action

然后閉包仍然超過原始值,因為在將它傳遞給getAction時會復制引用(而不是對象)。

請注意,如果調用者更改了原始對象的 ,則該方法將顯示以下內容:

x = someA();
var action = getAction(x);
x.name = "something else"; // seen by action

getAction方法中,它基本上是:

var tmp = new SomeCompilerGeneratedType();
tmp.obj = obj;
return new Action(tmp.SomeCompilerGeneratedMethod);

有:

class SomeCompilerGeneratedType {
    public Foo obj;
    public void SomeCompilerGeneratedMethod() {
        Console.WriteLine(obj.name); 
    }
}

簡短回答:解釋是正確的,如果要將值從x1更改為x2則必須更改傳遞給操作的特定對象。

obj1.name = 'x2'

當一個對象作為參數傳遞給一個函數時,它會將引用(指針)復制到obj。

那時你有一個對象和兩個引用;

  • Foo obj1Main和的變量
  • Foo objgetAction的變量

無論何時選擇將另一個對象(或null)設置為obj1它都不會影響getAction的第二個引用。

這是為Main生成的IL:

IL_0000:  ldstr       "x1"
IL_0005:  newobj      UserQuery+Foo..ctor
IL_000A:  stloc.0     // obj1
IL_000B:  ldloc.0     // obj1
IL_000C:  call        UserQuery.getAction
IL_0011:  stloc.1     // a
IL_0012:  ldstr       "x2"
IL_0017:  newobj      UserQuery+Foo..ctor
IL_001C:  stloc.0     // obj1
IL_001D:  ldloc.1     // a
IL_001E:  callvirt    System.Action.Invoke
IL_0023:  ret      

從中我推斷出當你調用getAction() ,它會為obj1創建帶有值的方法,當你創建一個新實例並調用委托時,由於它在編譯器創建的方法中具有先前的值,由於它打印x1

當你調用getAction(obj1)Foo obj現在引用新的Foo(“X1”)``,然后你正在做obj1 = new Foo("x2") ,現在obj1引用了new Foo("x2")Foo objgetAction(Foo obj)仍引用到new Foo("x1")

obj1 = new Foo("x1")  // obj1 is referencing to ----> Foo("x1") memory location
getAction(obj1)  // --> getAction(Foo obj) obj is referencing to Foo("x1")
obj1 = new Foo("x2") //  obj1 is now referencing to----> Foo("x2") memory location
// but in getAction(Foo obj) obj is still referencing to Foo("x1")

您可以將代碼重寫為

public class Program
{
    static void Main(string[] args)
    {
        Foo obj1 = new Foo("x1");
        // rewrite of
        // Action a = GetAction( obj1 );
        Foo obj = obj1;
        Action a = () => Console.WriteLine( obj.name );  

        obj1 = new Foo("x2");        
        a();                         
    }
}

這就是內部發生的事情。 您指定的參考obj和構建指的是動作obj

你的解釋是正確的,基本上是一種重新描述第5.1.4節中 C#語言規范所寫內容的方法(這里為了完整性而強調我的重點):

聲明沒有ref或out修飾符的參數是值參數。

值參數在調用參數所屬的函數成員(方法,實例構造函數,訪問器或運算符)(第7.4節)時存在,並使用調用中給定的參數值進行初始化。 返回函數成員時,值參數不再存在。

出於明確分配檢查的目的,最初分配值參數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM