2

Assume I have the following C# class ClassA:

public class ClassA
{
    private readonly ClassB _b;

    public ClassA(ClassB b)
    {
        _b = b;
    }
}

This class holds a reference to an instance of ClassB, which implements IDisposable:

public class ClassB : IDisposable
{
    public void Dispose()
    {
        // dispose
    }
}

Now, I register ClassA as a service via dependency injection, and create and pass an instance of ClassB to it:

services.AddSingleton(service => new ClassA(new ClassB()));

At the end of the lifetime of the service, is the Dispose() method of class B called? According to this stackoverflow post regarding the disposal of singleton instances, if ClassA would also implement IDisposable, then the Dispose() method of ClassA would also get called. Do I have to implement the interface also for ClassA, and call the Dispose() of ClassB within the Dispose() of ClassA? Or can I somehow be assured that the Dispose() method of ClassB gets called without implementing IDisposable in ClassA?

EDIT: I marked Philip Stuyck' answer as correct, since it seems to recommend the easiest solution for this problem. As to my question Is the Dispose() method of ClassB ever called?, I think the discussion below clearly states that it is not called. Instead, the GC cleans up the instance of ClassB by calling the Finalize method instead of the Dispose method on it.

dan-kli
  • 568
  • 2
  • 13
  • 2
    If a class owns `IDisposable`s then it should implement `Dispose()` like you described. In your case, however, `ClassA` does not actually own (i.e. manage the lifetime) of `ClassB`. Your startup code that does the registration owns the `ClassB` instance. At least that's how your code will be interpreted by convention. On the other hand this might be just fine if `ClassB` does not crash when its `Dispose()` is called twice. Have you considered injecting `ClassB` instead? – Good Night Nerd Pride May 31 '23 at 14:37
  • @GoodNightNerdPride: Of course `ClassA` manages the lifetime of `ClassB` and should implement `IDispoable` itself(as you said). It just doesn't manage it's own lifetime. But that's not important for `ClassB`. – Tim Schmelter May 31 '23 at 14:40
  • @TimSchmelter what I mean is that if a disposable instance of `B` is passed to the ctor of `A: IDisposable` then I would not automatically assume that `A` will call `b.Dispose()`. I'd rather expect that the code calling `A(B b)` will have a `using var b = new B()` somewhere. That might not always be the case, but if not then the docs of `A` would mention that it will call `b.Dispose()` on its own and an outside `using` is not required. – Good Night Nerd Pride May 31 '23 at 14:47
  • 1
    Related: https://stackoverflow.com/questions/30283564/dependency-injection-and-idisposable/30287923#30287923 – Steven May 31 '23 at 21:17
  • `Dispose on Windows is a task that is performed only when needed as part of GC (garbage collection).` That is not really true @jdweng. What makes you say that? – mjwills May 31 '23 at 23:21
  • @mjwills : Why do you disagree. It is the way GC works. – jdweng May 31 '23 at 23:47
  • 1
    @jdweng No it doesn't. GC explicitly does _not_ call `IDisposable.Dispose`. https://stackoverflow.com/questions/1691846/does-garbage-collector-call-dispose – mjwills Jun 01 '23 at 04:08
  • @mjwills : You are misinterpreting what I said. The dispose releases memory, but that memory is not usable until the GC adds it back into the memory pool. GC runs as a lower priority task in background and gets raised to a higher level when memory is low. – jdweng Jun 01 '23 at 08:52
  • You stated, and I quote `Dispose on Windows is a task that is performed only when needed as part of GC`. That simply isn't true. `IDisposable.Dispose` and GC are two completely independent things. "Dispose on Windows is a task that is performed only when needed as part of GC" is simply not a true statement since GC does not cause `Dispose`. – mjwills Jun 02 '23 at 12:11

2 Answers2

4

If you change the way you register things to your inversion of control container then you don't have to implement idisposable at all :

services.AddSingleton<ClassB>(ClassB);
services.AddSingleton<ClassA>(ClassA);

The moment you ask for an instance of ClassA, it will look at the constructor and see that it needs classB. So you let the container resolve ClassA and don't take control of this yourself.

Then when the application closes dispose will be called as needed on ClassB

Further improvements can be done by doing inversion of control all the way and make interfaces for both classA and classB. ClassA would then contain a reference to the interface instead of the class. Which is what dependency inversion is all about.

Steven
  • 166,672
  • 24
  • 332
  • 435
Philip Stuyck
  • 7,344
  • 3
  • 28
  • 39
1

According to your current setup, ClassA has a data member of ClassB, the latter being an implementation of IDisposable. Whenever you have a ClassA object, ClassB is instantiated, hence you should either make sure that ClassA implements IDisposable and disposes of its ClassB member (as long as it exists) or, you may choose an IoC (Inversion of Control) approach, as suggested by Philip Stuyck and then, due to the inversion you do not need to dispose of the ClassB member.

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175