簡體   English   中英

C# Lambdas 和“this”變量作用域

[英]C# Lambdas and “this” variable scope

我想知道我是否可以在 C# lambda 中使用this關鍵字,雖然實際上我知道我可以,但我想確保這不是一件壞事,或者以后會產生微妙的問題。

閱讀了lambdas 變量范圍的規則后,我可以看到:

在引用它的委托超出范圍之前,捕獲的變量不會被垃圾收集。

所以這讓我假設一個對象實例( this )也將被捕獲。 為了測試這一點,我寫了這個人為的例子,這是我想在我的真實代碼中大致瞄准的目標 - 用 LINQPad 編寫,因此我有Dump()方法調用:

void Main()
{
    Repository repo = new Repository();
    Person person = repo.GetPerson(1);

    person.ID.Dump("Person ID - Value Assigned");
    person.Name.Dump("Person Name - Lazily Created");
}

class Person
{
    public Person(Lazy<string> name)
    {
        this.name = name;
    }

    public int ID { get; set; }

    private Lazy<string> name;
    public string Name
    {
        get { return name.Value; }
    }
}

class Repository
{
    public Person GetPerson(int id)
    {
        // Setup person to lazily load a name value
        Person person = new Person(
            new Lazy<string>(
                () => this.GetName()    // <--- This I'm not sure on...
            )
        );
        person.ID = id;
        return person;
    }

    public string GetName()
    {
        return "John Smith";
    }
}

這會運行並為我提供正確的輸出,因此從 lambda 中訪問this顯然是有效的。 我想檢查的是:

  • 這是否遵循與局部變量相同的變量范圍規則,這意味着this引用保留在內存中,直到不再使用 lambda 為止? 從我的小實驗中看起來是這樣,但如果有人能提供更多細節,我會很感興趣。
  • 這是可取的嗎? 我不想稍后進入這種模式可能導致問題的情況。

在 lambda 中使用this沒有任何問題,但是正如您所提到的,如果您確實使用了this (或者如果您通過調用任何非靜態成員函數或使用非靜態成員變量隱式使用它),那么垃圾收集器將保留該對象至少只要代表還活着, this就是指活着。 由於您將 lambda 傳遞給Lazy ,這意味着至少只要Lazy對象還活着(即使您從未調用Lazy.Value ), Repository就會活着。

為了稍微揭開它的神秘面紗,它有助於查看反匯編程序。 考慮這個代碼:

class Foo {
    static Action fLambda, gLambda;

    int x;
    void f() {
        int y = 0;
        fLambda = () => ++y;
    }
    void g() {
        int y = 0;
        gLambda = () => y += x;
    }
}

標准編譯器將其更改為以下內容(嘗試忽略<>額外的尖括號)。 如您所見,使用函數體內部變量的 lambda 被轉換為類:

internal class Foo
{
    private static Action fLambda;
    private static Action gLambda;
    private int x;

    private void f()
    {
        Foo.<>c__DisplayClass1 <>c__DisplayClass = new Foo.<>c__DisplayClass1();
        <>c__DisplayClass.y = 0;
        Foo.fLambda = new Action(<>c__DisplayClass.<f>b__0);
    }
    private void g()
    {
        Foo.<>c__DisplayClass4 <>c__DisplayClass = new Foo.<>c__DisplayClass4();
        <>c__DisplayClass.<>4__this = this;
        <>c__DisplayClass.y = 0;
        Foo.gLambda = new Action(<>c__DisplayClass.<g>b__3);
    }

    [CompilerGenerated]
    private sealed class <>c__DisplayClass1
    {
        public int y;
        public void <f>b__0()
        {
            this.y++;
        }
    }
    [CompilerGenerated]
    private sealed class <>c__DisplayClass4
    {
        public int y;
        public Foo <>4__this;
        public void <g>b__3()
        {
            this.y += this.<>4__this.x;
        }
    }

}

如果您使用this ,無論是隱式還是顯式,它都會成為編譯器生成的類中的成員變量。 因此,對於類f()DisplayClass1 ,不包含參考Foo ,但對於類g()DisplayClass2 ,確實。

如果 lambda 不引用任何局部變量,則編譯器會以更簡單的方式處理它們。 所以考慮一些稍微不同的代碼:

public class Foo {
    static Action pLambda, qLambda;

    int x;
    void p() {
        int y = 0;
        pLambda = () => Console.WriteLine("Simple lambda!");
    }
    void q() {
        int y = 0;
        qLambda = () => Console.WriteLine(x);
    }
}

這次 lambda 不引用任何局部變量,因此編譯器將您的 lambda 函數轉換為普通函數。 p()中的 lambda 不使用this所以它變成了一個靜態函數(稱為<p>b__0 ); q()中的 lambda 確實使用了this (隱式),因此它變成了一個非靜態函數(稱為<q>b__2 ):

public class Foo {
    private static Action pLambda, qLambda;

    private int x;
    private void p()
    {
        Foo.pLambda = new Action(Foo.<p>b__0);
    }
    private void q()
    {
        Foo.qLambda = new Action(this.<q>b__2);
    }
    [CompilerGenerated] private static void <p>b__0()
    {
        Console.WriteLine("Simple lambda!");
    }
    [CompilerGenerated] private void <q>b__2()
    {
        Console.WriteLine(this.x);
    }
    // (I don't know why this is here)
    [CompilerGenerated] private static Action CS$<>9__CachedAnonymousMethodDelegate1;
}

:我認為使用編譯器輸出ILSpy關閉選項“反編譯匿名方法/ lambda表達式”。

雖然在這樣的 lambda 中使用this是正確的,但您只需要注意您的Repository對象在您的Person對象可垃圾回收之前不會是可垃圾回收的。

您可能希望有一個字段來緩存 lambda 的結果,一旦它被延遲填充,請釋放 lambda,因為您不再需要它了。

就像是:

private Lazy<string> nameProxy; 
private string name;
public string Name 
{ 
  get 
  {
    if(name==null)
    {
      name = nameProxy.Value;
      nameProxy = null;
    }
    return name;
  } 
} 

這是absolutelly蠻好用的this在lambda表達式,但有一些東西,你應該記住:

  • this將保留在內存中,直到不再使用 lambda
  • 如果你不在課堂外“用this ”傳遞 lambda,那么你就不會遇到問題
  • 如果你確實在你的班級之外“用this ”傳遞了 lambda,那么你應該記住,在有對 lambda 的引用之前, GC不會收集你的班級。

與您的用例相關,您應該記住,在它創建的人被使用之前, GC永遠不會收集Repository實例。

暫無
暫無

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

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