访问范围外的类实例的字段

时间:2018-11-21 12:40:11

标签: c# scope garbage-collection closures

让我们考虑一下这堂课:

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将不再存在,并且当最终从某个地方调用该函数时,该函数将尝试访问不存在的字段,但事实并非如此。该代码似乎有效。字段是否以某种方式保留在闭包中?

感谢您的澄清。

2 个答案:

答案 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()进行演示。通常,在生产代码中像这样称呼它是不必要的(也不可取)。