5

I recently came across this SO article and tweaked it for my scenario which follows:

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);
            }
        }
    }
}

Running this program gives the following expected result:

True
False
Press any key to continue . . .

However change this:

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

To this (using Object Initializer syntax):

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

Gives the following output:

True
True
Press any key to continue . . .

What's going on here? Why would lifetime of the reference be different just because of using Object Initializer?

Community
  • 1
  • 1
Jim
  • 4,910
  • 4
  • 32
  • 50
  • Hmm... it prints "False, False" for me. I can't get it to print True at all. Are you running this under the debugger? Debug or Release build? – Jon Skeet Aug 21 '13 at 20:46
  • @JonSkeet-Running under the debugger, yes. – Jim Aug 21 '13 at 20:48
  • I tryed it with unit tests, same results as posted. It's pretty cool, and thatswhy you shouldn't mess with GC :D – Vladislav Qulin Aug 21 '13 at 20:50
  • @JonSkeet-I recompiled under Release mode and now I get False, False – Jim Aug 21 '13 at 20:51
  • @Jim, your reported result is reproducible in release build on every run on my system *IF* it is run with the debugger attached. It produces "false, false" if run from commandline. – Alex Aug 21 '13 at 20:52
  • @Jim: Even under the debugger, still? That surprises me a bit, but I still suspect it's for the reason given in my answer, ultimately. – Jon Skeet Aug 21 '13 at 20:54
  • @JonSkeet-Building for Debug, I get True, False. Building for Release I get False, False. Interesting. If I actually step thru the program Debug or Release builds, I get the True, False output. – Jim Aug 21 '13 at 20:58
  • 1
    @Jim the reason that happens is when the debugger is attached the GC must assume that you could at any point pause the program and add a Watch Variable on any previously declared but no-longer used variable. Because of that the object will be kept alive for as long as you potentially could have a way to view it in the debugger. By running without the debugger attached the GC knows no one is going to pause the program and attach to a watch variable so it makes the object available for collection as soon as it is no longer used in the code. – Scott Chamberlain Aug 21 '13 at 21:22

1 Answers1

7

Why would lifetime of the reference be different just because of using Object Initializer?

I can't actually reproduce your problem anyway, but I suspect it's because this:

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

is equivalent to:

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

... so you end up with an extra reference to the object on the stack. Now when running not under the debugger, I'd expect the GC to notice that that stack location is never read again, and allow the object to be garbage collected - but as you're running under the debugger, the GC is more conservative, and I suspect it treats all stack variables as GC roots.

That's just a guess though - without being able to reproduce it anyway, it's hard to say for sure.

EDIT: Your assignments of obj = null; and refContainer = null; are pointless under non-debug mode; because the variables aren't read after that point anyway, the GC ignores them as GC roots.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • So my intent was to determine if a WeakReference can be used to reliably determine if an object is no longer referenced and thus take some action with a containing object. Is this a good idea? – Jim Aug 21 '13 at 21:06
  • @Jim: Do you mean you want to take some action when the object *isn't* referenced any longer? Because the weak reference will lose its target... – Jon Skeet Aug 21 '13 at 21:07
  • Yes, that's what I would like to do. Essentially, when the reference count goes to zero, do something. – Jim Aug 21 '13 at 21:10
  • And to clarify, I don't exactly know what the object is just that I want to track if it is referenced anywhere. – Jim Aug 21 '13 at 21:13
  • @Jim: .NET doesn't use reference counting. It sounds like you basically want a finalizer. You could consider wrapping the object you're actually interested in within an object which has a finalizer. But I would generally discourage this sort of thing - it's brittle, hard to debug, hard to test etc. – Jon Skeet Aug 21 '13 at 21:14
  • I know :-) That's my quandary. But at least I know a little more now. Thanks. – Jim Aug 21 '13 at 21:18