views:

202

answers:

6

I have a few classes that hold references to other classes through IDictionary instance members.

Like so:

class A
{
  private readonly Dictionary<int, B> _particles = new Dictionary<int, B>();
  public void CreateNewB(int someInt)
   {
     var b = new B();
     if (!_particles.ContainsKey(someInt)
         _particles.Add(someInt, b);
   }
}

so this is the setup, and I NEVER remove them from this dictionary, but for some reason, the destructor for class B gets called on a GC run every now and then and I dont understand why.

Could it be something to do with how the Dictionary class adds new references?

FIXED:

Ok, thank you all for your answers, I have certainly gain a large understanding about the GC and deconstructors now.

But the issue was my own, I was adding someInt only if it did not exist already and through flawed business logic, someInt was always 1, so the first time through it worked and the deconstructors did not get called. But the second time though, the "b" instance was simply not added to the list and was cleaned up in the GC run.

Thanks again to all who helped out!

+3  A: 

Your class B will be GC if the reference to the class A is dead

Dennis Cheung
I think it's safe to assume (Mark, please correct) that he is still holding a reference to his instance of `A` ("I NEVER remove them from this dictionary"; I assume this means that dictionary, and thus his instance of `A` still exists).
Jason
that is correct, I have created Console statements in the Destructor of class A and it never gets called
Mark
+1  A: 

I NEVER remove them from this dictionary, but for some reason, the destructor for class B gets called

The destructor in .NET is not the same as destructor in C++ (non-managed).
Destructor calls Finalize method automatically.

Here are some characteristics of a destructor:

  • Destructors cannot be defined in structs. They are only used with classes.
  • A class can only have one destructor.
  • Destructors cannot be inherited or overloaded.
  • Destructors cannot be called. They are invoked automatically.
  • A destructor does not take modifiers or have parameters.

So what is happening in your case is this (in 2 words):

  1. Class A is Garbage Collected.
  2. As a result of 1: _particles field get GC-d.
  3. As a result of 2: entries in the Dictionary became non-rooted (available for Garbage Collection).
  4. As a result of 3: entries in the Dictionary (class B instances) are GC-d.

Could it be something to do with how the Dictionary class adds new references?

No.

Dmytrii Nagirniak
I thought this was the case too, so I put Console statements in my destructor of class A, and it never gets called at all...
Mark
You do not have destructor in the question. If you do it changes a lot. So you'd better provide the all the problematic code.
Dmytrii Nagirniak
A: 

I would bet my money on class B being created elsewhere. If it's a console app, output something in B constructor and finalizer. Make sure that the number of instantiations is what you expect it to be.

Igor Zevaka
A: 

So I have the following type:

type k {
public k() { Console.WriteLine("Hi, I'm a new K!"); }
public ~k() { Console.WriteLine("I'm a dying K!"); }
}

And a small snippet of code:

Dictionary<int, k> ks = new Dictionary<int, k);
for(int i=0;i<10;i++) { ks.add(i, new k()); }

and you see that ~k() is being callled at one point? Is that whats going on?

Indrora
+1  A: 

It sounds like you're doing something very wrong. In a managed environment, keeping a reference around like this that will essentially live forever amounts to a memory leak, unless you really mean to keep these objects around.

The other thing to remember here is that there's no such thing as a destructor in C#. What you have are finalizers, which are different. It's rare with managed code that you should even need to write a finalizer at all. The only good reason to do so is when you are implementing IDisposable for a type to wrap an unmanaged resource that's not already covered by a finalizer.

For example, lots of people create a type that implements IDisposable and wraps SqlConnection as part of their data access layer. This way they can wrap instances of the type in using blocks and make sure any SqlConnections they create are disposed properly. But this type does not need a finalizer, because the underlying database connection is already covered by the finalizer in the SqlConnection class itself. There's no new unmanaged resource type to worry about, just the SqlConnection type. But if you were building a whole new database engine and were implementing the new .Net data provider for it, you would want to implement a finalizer for your connection.

Joel Coehoorn
Great stuff Joel. I find it interesting how the finalizer seems to be one of the least understood concepts in .NET. Plus one.
Jason
A: 

Ok, thank you all for your answers, I have certainly gain a large understanding about the GC and deconstructors now.

But the issue was my own, I was adding someInt only if it did not exist already and through flawed business logic, someInt was always 1, so the first time through it worked and the deconstructors did not get called. But the second time though, the "b" instance was simply not added to the list and was cleaned up in the GC run.

I am answering this just to close it off :)

Mark