C#lambda表达式和“这个”变量的作用域(C# Lambdas and “this” varia

2019-07-30 00:41发布

我想知道我是否可以使用this一个C#拉姆达里面的关键字,但其实我知道我可以,但我要确保这不是一件坏事或稍后会产生微妙的问题。

已经阅读规则的lambda表达式变量的作用域 ,我可以看到:

被捕获的将不会是一个可变的垃圾收集,直到引用它超出范围的代表。

因此,这导致我假设一个对象实例( 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";
    }
}

这将运行并给我正确的输出,所以访问this从内拉姆达清楚地工作。 我所想,虽然检查的是:

  • 这是否遵循相同的变量范围规则的局部变量,这意味着this参考保存在内存中,直到拉姆达不再使用? 它会从我的小实验,让出现,但如果有人能提供更多细节我有兴趣。
  • 这是可取的? 我并不想进入的情况后,其中这种模式可能会导致问题。

Answer 1:

没有什么错误使用this在一个lambda,但你提到,如果你使用this (或者,如果你使用它含蓄,通过调用任何非静态成员函数或使用非静态成员变量),那么垃圾收集器将保持对象那this是指活着,至少只要委托是活的。 既然你传递一个lambda来Lazy ,这意味着Repository一定会健在至少只要Lazy对象是活的(即使你从来没有叫Lazy.Value )。

为了揭穿它一下,它有助于反汇编的样子。 考虑以下代码:

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函数为普通的功能。 在lambda p()不使用this所以它成为一个静态函数(称为<p>b__0 ); 在lambda q()确实使用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表达式” 关闭



Answer 2:

虽然这是正确的使用this在那样的拉姆达,你只需要知道你的Repository对象不会被垃圾收集的,直到你的Person目标是当作垃圾收集。

你可能想有一个字段从您的拉姆达缓存的结果,而一旦它被懒填充,释放拉姆达,因为你不需要它了。

就像是:

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


Answer 3:

这是absolutelly蛮好用的this在lambda表达式,但有一些东西,你应该记住:

  • this将被保存在内存中,直到拉姆达不再使用
  • 如果你没有通过拉姆达“用this ”类的外部,那么你就不会面临的问题
  • 如果你通过拉姆达“用this ”类的外部,那么你应该记住,你的类将不会被收集GC直到有到拉姆达左引用。

并与您的使用情况,你应该记住的是, Repository实例将永远不会被收集GC ,直到它创造的人都在使用。



文章来源: C# Lambdas and “this” variable scope