views:

93

answers:

6

I was recently going through a garbage collection article and decided to play along and attempt to gain a greater understanding. I coded the following, playing around with the using statement, but was surprised with the results... I expected e.Parent.Name outside of the using block to go ka-blooey.

What exactly is going on here?

static void Main(string[] args)
        {
            Employee e = new Employee();

            using (Parent p = new Parent())
            {
                p.Name = "Betsy";
                e.Parent = p;
                Console.WriteLine(e.Parent.Name);
            }

            Console.WriteLine(e.Parent.Name);            

            Console.ReadLine();
        }

        public class Employee
        {
            public Parent Parent;
        }

        public class Parent : IDisposable
        {
            public string Name;

            public void Dispose()
            {
                Console.WriteLine("Disposing Parent");
            }
        }
+10  A: 

Your Dispose method doesn't actually do anything to the instance of Parent, hence it's still fair game / works as a usable instance of a class.

IDisposable is usually used when your class holds onto an unmanaged resource, such as a database connection or a file, so that it can be cleaned up when Dispose() is called. Just calling Dispose doesn't do anything to the unmanaged resources, there has to be some code in the method that does something to those resources. Whilst c# might have the using() {} syntax to wrap instantiation and disposal of an IDisposable object in a try/catch/finally, it doesn't mean it does anything "special" with the disposed object.

Imagine, hypothetically, that Name is actually an unmanaged resource, rather than just a string, your Dispose() method could read:

public void Dispose()
{
    Name = null;
    Console.WriteLine("Disposing Parent");
}

Because you've assigned p to e.Parent, the object itself is still "in scope" as there's a reference to it, hence it's still accessible for Console.WriteLine(e.Parent.Name); to produce output from.

It's also currently "CLR Week" over at The Old New Thing and the first 3 articles of the week are discussing the Garbage Collector and how it works/behaves. They're well worth a read:

Rob
Ahh, of course. So nothing 'magical' is happening just because I put it into a using block. It's the same as if I had passed that Employee into a method, created the Parent in the method and set it to the Employee. Outside of the method, Parent cannot be totally collected because it's still referenced. Is that correct?
Mike M.
@Mike, it's exactly that =)
Rob
+1  A: 

Since e still exists in scope that anything associated with e (the parent assigned to e) will still exist until e is out of scope.

Scott Lance
+1  A: 

IDisposable.Dispose is intended for you to use it to clean up unmanaged resources that you own (like file handles etc.) It doesn't do anything in its own right. The most common usage is if your class had member variables that implement IDisposable themselves, you now have responsibility for Dispose'ing them. It's just a pattern to help you, and has nothing to do with Garbage Collection - quite the opposite in fact.

x0n
+1  A: 

The Dispose method does not destroy the object from memory. Normally a dispose method will only free up resources that it created.

Jerod Houghtelling
A: 

IDisposable is not a language feature, and does nothing special in the runtime. It is just an interface/method like any other. It happens to be a useful pattern, so they added syntax to automatically call that method in a specific pattern (using), and there are special exceptions you can throw that have "Dispose" in their name (like ObjectDisposedException).

using blocks turn from this:

using(SomeType t = new SomeType())
{
  t.Something();
}

into something like this:

{
  SomeType t;

  try
  {
    t = new SomeType();
    t.Something();
  }
  finally
  {
    t.Dispose();
  }
}

There is absolutely no way to force the GC to collect anything. If there are references to your object somewhere in the stack (ignoring unsafe and C++/CLI code), or chained references to it from some object on the stack, then your object will live.

If you want that code to blow up, you can do something like this:

public class Parent : IDisposable
{
    public string Name
    {
        get
        {
            AssertNotDisposed();
            return name;
        }
        set
        {
            AssertNotDisposed();
            name = value;
        }
    }

    public void Dispose()
    {
        AssertNotDisposed();
        Console.WriteLine("Disposing Parent");
        isDisposed = true;
    }

    private void AssertNotDisposed()
    {
        if(isDisposed)
            throw new ObjectDisposedException("some message");
    }

    private string name;
    private bool isDisposed = false;
}
Merlyn Morgan-Graham
A: 

If you're looking for another example that will blow up when trying to do something to a disposed object.

static void Main(string[] args)
        {
        Employee e = new Employee();

        using (Parent p = new Parent("test.txt"))
        {

            e.Parent = p;



          using ( System.IO.StreamWriter fileWriter  = 
                new System.IO.StreamWriter(e.Parent.File))
                {
                fileWriter.WriteLine("Betsy");
            }

        }



   using (System.IO.StreamWriter fileWriter =
           new System.IO.StreamWriter(e.Parent.File))
        {
            fileWriter.WriteLine("Betsy"); //uh-oh
        }


        Console.ReadLine();
    }


    public class Employee
    {
        public Parent Parent;
    }

    public class Parent : IDisposable
    {
        public System.IO.FileStream File;

        public Parent(string fileName)
        {
            File = System.IO.File.Open(fileName, System.IO.FileMode.OpenOrCreate);

        }

        public void Dispose()
        {
            ((IDisposable)File).Dispose(); //or File.Close();
        }
    }
Conrad Frix