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.
Having read the rules on variable scope for lambdas, I can see that:
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. 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:
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. 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? 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 usethis
(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 thatthis
refers to alive at least as long as the delegate is alive. Since you pass a lambda toLazy
, this implies that theRepository
will be alive at least as long as theLazy
object is alive (even if you never callLazy.Value
).To demystify it a bit, it helps to look in a disassembler. Consider this code:
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:If you use
this
, whether implicitly or explicitly, it becomes a member variable in the compiler-generated class. So the class forf()
,DisplayClass1
, does not contain a reference toFoo
, but the class forg()
,DisplayClass2
, does.The compiler handles lambdas in a simpler manner if they don't reference any local variables. So consider some slightly different code:
This time the lambdas don't reference any local variables, so the compiler translates your lambda functions into ordinary functions. The lambda in
p()
does not usethis
so it becomes a static function (called<p>b__0
); the lambda inq()
does usethis
(implicitly) so it becomes a non-static function (called<q>b__2
):Note: I viewed the compiler output using ILSpy with the option "decompile anonymous methods/lambdas" turned off.
While it is correct to use
this
in a lambda like that, you just need to be aware that yourRepository
object will not be garbage collectable until yourPerson
object is garbage collectable.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.
Something like:
It is absolutelly fine to use
this
in lambdas, but there is some stuff you should keep in mind:this
will be kept in memory until the lambda is not used anymorethis
" outside your class, then you will not face problemsthis
" outside your class, then you should remember, that your class will not be collected by theGC
until there are references to the lambda left.And related to your use-case, you should keep in mind that the
Repository
instance will never be collected by theGC
until people it created are in use.