14

我想知道我是否可以this在 C# lambda 中使用关键字,虽然实际上我知道我可以,但我想确保这不是一件坏事,或者以后会产生微妙的问题。

阅读了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";
    }
}

这运行并为我提供了正确的输出,因此this从 lambda 中访问显然有效。我想检查的是:

  • 这是否遵循与局部变量相同的变量范围规则,这意味着this引用将保留在内存中,直到不再使用 lambda?从我的小实验中可以看出,但如果有人能提供更多细节,我会很感兴趣。
  • 这是可取的吗?我不想在以后陷入这种模式可能导致问题的情况。
4

3 回答 3

14

在 lambda中使用没有任何问题this,但是正如您所提到的,如果您确实使用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

如果 lambdas 不引用任何局部变量,编译器会以更简单的方式处理它们。所以考虑一些稍微不同的代码:

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 函数转换为普通函数。in 中的 lambdap()不使用this,因此它成为一个静态函数(称为<p>b__0);lambda inq()确实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查看了编译器输出,并关闭了“反编译匿名方法/lambdas”选项。

于 2012-06-19T16:43:54.663 回答
1

虽然this在这样的 lambda 中使用是正确的,但您只需要注意,在您的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;
  } 
} 
于 2012-06-19T15:17:17.520 回答
0

在 lambdas中使用绝对this没问题,但有一些事情你应该记住:

  • this将保留在内存中,直到不再使用 lambda
  • 如果你没有this在课堂外通过 lambda "with",那么你就不会遇到问题
  • 如果你确实在你的类之外传递了 lambda "with ",那么你应该记住,在剩下对 lambda 的引用之前this,你的类不会被收集。GC

并且与您的用例相关,您应该记住,在它创建的人正在使用之前,Repository实例永远不会被收集。GC

于 2012-06-19T15:08:27.263 回答