[英]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
。
現在我的問題是:
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 obj1
是Main
和的變量 Foo obj
是getAction
的變量 無論何時選擇將另一個對象(或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 obj
的getAction(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.