让我们考虑一下这堂课:
public class A<T>
{
private bool _flag;
public Func<T> Function { get; set; } = () => {_flag = true; return _flag; }
}
现在,让我们想象一下,Function属性以某种方式在其主体中以读写方式访问_flag字段。然后,如果我这样使用A类:
public Func<T> SomeFunction()
{
var instance = new A();
return instance.Function;
}
我的问题是真正发生的情况,因为我最初假设该实例将在SomeFunctions返回时由GC处理,这意味着_flag将不再存在,并且当最终从某个地方调用该函数时,该函数将尝试访问不存在的字段,但事实并非如此。该代码似乎有效。字段是否以某种方式保留在闭包中?
感谢您的澄清。
答案 0 :(得分:0)
如果仍有实例引用,GC将不会删除该实例。包含对字段的引用的Func <>将保存对对象(关闭)的引用。
答案 1 :(得分:0)
为了简单起见,我将使用Dummy
类(而不是您的通用A
):
class Dummy
{
public int i = 0;
~Dummy()
{
Console.WriteLine("~Dummy --> " + i);
}
}
注意 finalizer ,它使我们可以看到GC收集确切时间。
现在,考虑这个辅助函数:它创建一个新的Dummy d
,然后将其包装在函数闭包中并返回该闭包。
static Func<int> MakeFunc()
{
Dummy d = new Dummy();
Func<int> f = () => {
d.i++;
Console.WriteLine("Func invoked, d.i is now " + d.i);
return d.i;
};
return f;
}
我们可以这样称呼它:
public static void Main()
{
Console.WriteLine("1");
Func<int> f = MakeFunc();
f();
Console.WriteLine("2");
输出:
1
Func invoked, d.i is now 1
2
我们可以强制垃圾回收周期:
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("3");
输出:
3
请注意,我们还没有看到 finalizer 运行! Dummy
对象仍处于活动状态,由函数闭包保留。
实际上,我们可以继续调用该函数:
f();
Console.WriteLine("4");
输出:
Func invoked, d.i is now 2
4
当我们删除引用并强制执行另一个垃圾回收周期时,该Dummy
实例将最终确定:
f = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("5");
输出:
~Dummy --> 2
5
P.S。:在这里,我使用GC.Collect()
进行演示。通常,在生产代码中像这样称呼它是不必要的(也不可取)。