Why does using an Object Initializer keep an object alive?
Asked Answered
C

1

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?

Charlot answered 21/8, 2013 at 20:42 Comment(8)
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?K2
@JonSkeet-Running under the debugger, yes.Charlot
I tryed it with unit tests, same results as posted. It's pretty cool, and thatswhy you shouldn't mess with GC :DConvenient
@JonSkeet-I recompiled under Release mode and now I get False, FalseCharlot
@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.Haematoma
@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.K2
@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.Charlot
@Charlot 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.Leialeibman
K
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.

K2 answered 21/8, 2013 at 20:49 Comment(6)
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?Charlot
@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...K2
Yes, that's what I would like to do. Essentially, when the reference count goes to zero, do something.Charlot
And to clarify, I don't exactly know what the object is just that I want to track if it is referenced anywhere.Charlot
@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.K2
I know :-) That's my quandary. But at least I know a little more now. Thanks.Charlot

© 2022 - 2024 — McMap. All rights reserved.