【问题标题】:Why does using an Object Initializer keep an object alive?为什么使用对象初始化器可以使对象保持活动状态?
【发布时间】:2013-08-24 09:46:42
【问题描述】:

我最近遇到了这个SO article 并针对我的以下场景进行了调整:

using System;
using System.Collections.Generic;

namespace ConsoleApplication18
{
    class Program
    {
        static void Main(string[] args)
        {
            Manager mgr = new Manager();
            var obj = new byte[1024];

            var refContainer = new RefContainer();
            refContainer.Target = obj;

            obj = null;

            mgr["abc"] = refContainer.Target;

            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            Console.WriteLine(mgr["abc"] != null); // true (still ref'd by "obj")

            refContainer = null;

            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            Console.WriteLine(mgr["abc"] != null); // false (no remaining refs)           
        }
    }

    class RefContainer
    {
        public object Target { get; set; }
    }

    class Manager
    {
        Dictionary<string, WeakReference> refs =
        new Dictionary<string, WeakReference>();
        public object this[string key]
        {
            get
            {
                WeakReference wr;
                if (refs.TryGetValue(key, out wr))
                {
                    if (wr.IsAlive)
                        return wr.Target;
                    refs.Remove(key);
                }
                return null;
            }
            set
            {
                refs[key] = new WeakReference(value);
            }
        }
    }
}

运行此程序会得到以下预期结果:

True
False
Press any key to continue . . .

但是改变这个:

var refContainer = new RefContainer();
refContainer.Target = obj;

对此(使用对象初始化器语法):

var refContainer = new RefContainer() { Target = obj };

给出以下输出:

True
True
Press any key to continue . . .

这里发生了什么?为什么引用的生命周期会因为使用 Object Initializer 而不同?

【问题讨论】:

  • 嗯...它为我打印“假,假”。我根本无法让它打印 True 。你是在调试器下运行这个吗?调试还是发布版本?
  • @JonSkeet-在调试器下运行,是的。
  • 我用单元测试进行了尝试,结果与发布的相同。这很酷,这就是为什么你不应该搞乱 GC :D
  • @JonSkeet-我在发布模式下重新编译,现在我得到了 False,False
  • @Jim 发生这种情况的原因是,当附加了调试器时,GC 必须假定您可以随时暂停程序并在任何先前声明但不再使用的变量上添加一个监视变量。因此,只要您有可能在调试器中查看它,该对象就会一直保持活动状态。通过在没有附加调试器的情况下运行,GC 知道没有人会暂停程序并附加到监视变量,因此一旦代码中不再使用该对象,它就可以立即进行收集。

标签: c# garbage-collection weak-references object-initializers


【解决方案1】:

为什么引用的生命周期会因为使用 Object Initializer 而不同?

我实际上无法重现您的问题,但我怀疑这是因为:

var refContainer = new RefContainer() { Target = obj };

相当于:

var tmp = new RefContainer();
tmp.Target = obj;
var refContainer = tmp;

... 所以你最终会额外引用堆栈上的对象。现在,当不在调试器下运行时,我期望 GC 会注意到该堆栈位置不再被读取,并允许对对象进行垃圾收集 - 但是当您在调试器下运行时,GC 更加保守,我怀疑它会将 all 堆栈变量视为 GC 根。

这只是一个猜测 - 无论如何都无法重现它,很难确定。

编辑:obj = null;refContainer = null; 的分配在非调试模式下毫无意义;因为在那之后变量不会被读取,GC 会忽略它们作为 GC 根。

【讨论】:

  • 所以我的意图是确定是否可以使用 Wea​​kReference 可靠地确定对象是否不再被引用,从而对包含对象采取一些操作。这是个好主意吗?
  • @Jim:你的意思是当对象不再被引用时你想采取一些行动?因为弱引用会失去目标...
  • 是的,这就是我想做的。本质上,当引用计数变为零时,做点什么。
  • 澄清一下,我并不完全知道我想要跟踪的对象是什么,如果它在任何地方被引用。
  • @Jim:.NET 不使用引用计数。听起来你基本上想要一个终结器。您可以考虑将您真正感兴趣的对象包装在具有终结器的对象中。但我通常不鼓励这种事情——它很脆弱、难以调试、难以测试等。
猜你喜欢
  • 1970-01-01
  • 2013-06-27
  • 1970-01-01
  • 2013-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多