我想知道我是否可以使用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
参考保存在内存中,直到拉姆达不再使用? 它会从我的小实验,让出现,但如果有人能提供更多细节我有兴趣。 - 这是可取的? 我并不想进入的情况后,其中这种模式可能会导致问题。
没有什么错误使用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表达式” 关闭 。
虽然这是正确的使用this
在那样的拉姆达,你只需要知道你的Repository
对象不会被垃圾收集的,直到你的Person
目标是当作垃圾收集。
你可能想有一个字段从您的拉姆达缓存的结果,而一旦它被懒填充,释放拉姆达,因为你不需要它了。
就像是:
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
将被保存在内存中,直到拉姆达不再使用 - 如果你没有通过拉姆达“用
this
”类的外部,那么你就不会面临的问题 - 如果你通过拉姆达“用
this
”类的外部,那么你应该记住,你的类将不会被收集GC
直到有到拉姆达左引用。
并与您的使用情况,你应该记住的是, Repository
实例将永远不会被收集GC
,直到它创造的人都在使用。