简体   繁体   English

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

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

I am wondering whether I can use the this keyword inside a C# lambda, although actually I know that I can but I want to make sure that this isn't a bad thing or will produce subtle issues later on.我想知道我是否可以在 C# lambda 中使用this关键字,虽然实际上我知道我可以,但我想确保这不是一件坏事,或者以后会产生微妙的问题。

Having read the rules on variable scope for lambdas , I can see that:阅读了lambdas 变量范围的规则后,我可以看到:

A variable that is captured will not be garbage-collected until the delegate that references it goes out of scope.在引用它的委托超出范围之前,捕获的变量不会被垃圾收集。

So this leads me to assume that an object instance ( this ) will also be captured.所以这让我假设一个对象实例( this )也将被捕获。 To test this I wrote this contrived example which is what I want to approximately aim for in my real code - written in LINQPad, hence why I have the Dump() method calls:为了测试这一点,我写了这个人为的例子,这是我想在我的真实代码中大致瞄准的目标 - 用 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 runs and gives me the correct output so accessing this from within a lambda clearly works.这会运行并为我提供正确的输出,因此从 lambda 中访问this显然是有效的。 What I am wanting to check though is:我想检查的是:

  • Does this follow the same variable scope rules as local variables, meaning that the this reference is kept in memory until the lambda is not used anymore?这是否遵循与局部变量相同的变量范围规则,这意味着this引用保留在内存中,直到不再使用 lambda 为止? It would appear so from my little experiment but if anyone can give further details I'd be interested.从我的小实验中看起来是这样,但如果有人能提供更多细节,我会很感兴趣。
  • Is this advisable?这是可取的吗? I do not want to get into the situation later where this pattern could cause problems.我不想稍后进入这种模式可能导致问题的情况。

There is nothing wrong with using this in a lambda, but as you mention, if you do use this (or if you use it implicitly, by calling any nonstatic member function or using a nonstatic member variable) then the garbage collector will keep the object that this refers to alive at least as long as the delegate is alive.在 lambda 中使用this没有任何问题,但是正如您所提到的,如果您确实使用了this (或者如果您通过调用任何非静态成员函数或使用非静态成员变量隐式使用它),那么垃圾收集器将保留该对象至少只要代表还活着, this就是指活着。 Since you pass a lambda to Lazy , this implies that the Repository will be alive at least as long as the Lazy object is alive (even if you never call Lazy.Value ).由于您将 lambda 传递给Lazy ,这意味着至少只要Lazy对象还活着(即使您从未调用Lazy.Value ), Repository就会活着。

To demystify it a bit, it helps to look in a disassembler.为了稍微揭开它的神秘面纱,它有助于查看反汇编程序。 Consider this code:考虑这个代码:

class Foo {
    static Action fLambda, gLambda;

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

The standard compiler changes this to the following (try to ignore the <> extra angle brackets).标准编译器将其更改为以下内容(尝试忽略<>额外的尖括号)。 As you can see, lambdas that use variables from inside the function body are transformed into classes:如您所见,使用函数体内部变量的 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;
        }
    }

}

If you use this , whether implicitly or explicitly, it becomes a member variable in the compiler-generated class.如果您使用this ,无论是隐式还是显式,它都会成为编译器生成的类中的成员变量。 So the class for f() , DisplayClass1 , does not contain a reference to Foo , but the class for g() , DisplayClass2 , does.因此,对于类f()DisplayClass1 ,不包含参考Foo ,但对于类g()DisplayClass2 ,确实。

The compiler handles lambdas in a simpler manner if they don't reference any local variables.如果 lambda 不引用任何局部变量,则编译器会以更简单的方式处理它们。 So consider some slightly different code:所以考虑一些稍微不同的代码:

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);
    }
}

This time the lambdas don't reference any local variables, so the compiler translates your lambda functions into ordinary functions.这次 lambda 不引用任何局部变量,因此编译器将您的 lambda 函数转换为普通函数。 The lambda in p() does not use this so it becomes a static function (called <p>b__0 ); p()中的 lambda 不使用this所以它变成了一个静态函数(称为<p>b__0 ); the lambda in q() does use this (implicitly) so it becomes a non-static function (called <q>b__2 ): 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;
}

Note : I viewed the compiler output using ILSpy with the option "decompile anonymous methods/lambdas" turned off .:我认为使用编译器输出ILSpy关闭选项“反编译匿名方法/ lambda表达式”。

While it is correct to use this in a lambda like that, you just need to be aware that your Repository object will not be garbage collectable until your Person object is garbage collectable.虽然在这样的 lambda 中使用this是正确的,但您只需要注意您的Repository对象在您的Person对象可垃圾回收之前不会是可垃圾回收的。

You might want to have a field to cache the result from your lambda, and once it is Lazy filled, release the lambda since you do not need it anymore.您可能希望有一个字段来缓存 lambda 的结果,一旦它被延迟填充,请释放 lambda,因为您不再需要它了。

Something like:就像是:

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

It is absolutelly fine to use this in lambdas, but there is some stuff you should keep in mind:这是absolutelly蛮好用的this在lambda表达式,但有一些东西,你应该记住:

  • this will be kept in memory until the lambda is not used anymore this将保留在内存中,直到不再使用 lambda
  • if you do not pass lambda "with this " outside your class, then you will not face problems如果你不在课堂外“用this ”传递 lambda,那么你就不会遇到问题
  • if you do pass lambda "with this " outside your class, then you should remember, that your class will not be collected by the GC until there are references to the lambda left.如果你确实在你的班级之外“用this ”传递了 lambda,那么你应该记住,在有对 lambda 的引用之前, GC不会收集你的班级。

And related to your use-case, you should keep in mind that the Repository instance will never be collected by the GC until people it created are in use.与您的用例相关,您应该记住,在它创建的人被使用之前, GC永远不会收集Repository实例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM