66

Starting from the following situation:

public interface ISample
{
}

public class SampleA : ISample
{
   // has some (unmanaged) resources that needs to be disposed
}

public class SampleB : ISample
{
   // has no resources that needs to be disposed
}

The class SampleA should implement the interface IDisposable for releasing resources. You could solve this in two ways:

1. Add the required interface to the class SampleA:

public class SampleA : ISample, IDisposable
{
   // has some (unmanaged) resources that needs to be disposed
}

2. Add it to the interface ISample and force derived classes to implement it:

public interface ISample : IDisposable
{
}

If you put it into the interface, you force any implementation to implement IDisposable even if they have nothing to dispose. On the other hand, it is very clear to see that the concrete implementation of an interface requires a dispose/using block and you don't need to cast as IDisposable for cleaning up. There might be some more pros/cons in both ways... why would you suggest to use one way preferred to the other?

Pang
  • 9,564
  • 146
  • 81
  • 122
Beachwalker
  • 7,685
  • 6
  • 52
  • 94
  • 5
    How likely is it that code written against the interface (presumably being handed an already constructed instance) is responsible for *ending* the (useful) lifetime of that instance? – Damien_The_Unbeliever May 09 '12 at 10:11
  • 3
    @Damien_The_Unbeliever: Ok, assuming the ISample comes from a factory method result or via Dependency Injection. – Beachwalker May 09 '12 at 11:25
  • 6
    That's the thing - if it *is* coming from a factory, then likely your code *is* responsible for disposal - so I'd put it on the Interface. But if it's being injected then I'd assuming the injector is responsible for the lifetime also, so it *wouldn't* fit on the interface - I don't think there's a one-size-fits-all answer to the question. – Damien_The_Unbeliever May 09 '12 at 11:28

7 Answers7

29

Following the Inteface Segregation Principle of SOLID if you add the IDisposable to the interface you are giving methods to clients that are not interested in so you should add it to A.

Apart from that, an interface is never disposable because disposability is something related with the concrete implementation of the interface, never with the interface itself.

Any interface can be potentially implemented with or without elements that need to be disposed.

Ignacio Soler Garcia
  • 21,122
  • 31
  • 128
  • 207
  • 8
    While in theory your suggestion is the most correct, in practice dealing with interfaces with potential disposable implementations is cumbersome. A lot depends on who manages the lifetime of the object. A better answer IMO here: https://stackoverflow.com/a/14368765/661933 – nawfal Dec 22 '18 at 09:03
  • Also it's easier for code analysis tools to warn if interfaces themselves implement IDisposable. – nawfal Dec 22 '18 at 09:05
  • 2
    @nawfal: if the theory does not match with the practice that means the tools we use are wrong, not the other way around. An interface, by definition, cannot be disposable as you only dispose implementations, not operation contracts. – Ignacio Soler Garcia Jan 04 '19 at 08:52
17

If you apply the using(){} pattern to all your interfaces it's best to have ISample derive from IDisposable because the rule of thumb when designing interfaces is to favor "ease-of-use" over "ease-of-implementation".

Moe Howard
  • 157
  • 9
Felice Pollano
  • 32,832
  • 9
  • 75
  • 115
  • how would you use a using statement in a foreach? – M Afifi May 23 '12 at 08:56
  • 3
    "... and you just see the interface..." That's the key. Many good, OOP solutions will only use the interface, so I think it makes sense for the interface to implement IDisposable. That way the consuming code can treat all of the subclasses the same way. – Bob Horn Jun 18 '12 at 01:05
7

Personally, if all ISample's should be disposable I'd put it on the interface, if only some are I'd only put it on the classes where it should be.

Sounds like you have the latter case.

Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • But how to ensure the calling of Dispose() if the user of your lib does not know it and there might be an implementation with is required to dispose? – Beachwalker May 09 '12 at 11:26
  • @Stegi - Easy. Just test `is IDisposable` and call `Dispose` if so. – Jamiec May 09 '12 at 11:30
  • Yes I know, but this would rubbish the code... because EVERY other implementation (e.g. IList, I...) could be IDisposable. Seems to me like IDisposable should be a general object base class implementation (even if empty). – Beachwalker May 09 '12 at 11:46
  • 1
    @Stegi - Then you've answered your own question really. – Jamiec May 09 '12 at 12:42
  • @Stegi assuming you're ok with the performance impact of a tryf – M Afifi May 09 '12 at 13:32
6

An interface IFoo should probably implement IDisposable if it is likely that at least some some implementations will implement IDisposable, and on at least some occasions the last surviving reference to an instance will be stored in a variable or field of type IFoo. It should almost certainly implement IDisposable if any implementations might implement IDisposable and instances will be created via factory interface (as is the case with instances of IEnumerator<T>, which in many cases are created via factory interface IEnumerable<T>).

Comparing IEnumerable<T> and IEnumerator<T> is instructive. Some types which implement IEnumerable<T> also implement IDisposable, but code which creates instances of such types will know what they are, know that they need disposal, and use them as their particular types. Such instances may be passed to other routines as type IEnumerable<T>, and those other routines would have no clue that the objects are eventually going to need disposing, but those other routines would in most cases not be the last ones to hold references to the objects. By contrast, instances of IEnumerator<T> are often created, used, and ultimately abandoned, by code which knows nothing about the underlying types of those instances beyond the fact that they're returned by IEnumerable<T>. Some implementations of IEnumerable<T>.GetEnumerator() return implementations of IEnumerator<T> will leak resources if their IDisposable.Dispose method is not called before they are abandoned, and most code which accepts parameters of type IEnumerable<T> will have no way of knowing if such types may be passed to it. Although it would be possible for IEnumerable<T> to include a property EnumeratorTypeNeedsDisposal to indicate whether the returned IEnumerator<T> would have to be disposed, or simply require that routines which call GetEnumerator() check the type of the returned object to see if it implements IDisposable, it's quicker and easier to unconditionally call a Dispose method that might not do anything, than to determine whether Dispose is necessary and call it only if so.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • `IEnumerator` is a good example of an interface where lifetime management is definitely part of its API. This was probably done to keep things simpler. However, it could be conceived that one would want to call `GetEnumerator()` in one method and at some point pass the enumerator to another method. That other method would ideally not have access to `Dispose()`. If the framework had `interface IDisposableEnumerator : IEnumerator, IDisposable {}` and `interface IEnumerator : IEnumerator { T Current { get; } }`, this could make some things “cleaner” (but more complex too :-/). – binki Oct 13 '17 at 15:44
  • 1
    @binki: What would have made things cleaner would have been for `IEnumerator` to implement `IDisposable`. Code which receives an `IEnumerable` it knows nothing about must presume that it may be necessary to call `Dispose` on an `IEnumerator` it returns. If it passes the returned enumerator to some other method for consumption at some later time, that latter method may have the responsibility of calling `Dispose` on it. Since calling a do-nothing `Dispose` on something that's known to implement `IDisposable` is faster than checking whether an object does implement `IDisposable`... – supercat Oct 13 '17 at 16:13
  • 1
    ...the simplest way for objects to fulfill their obligations would be to call `Dispose` on enumerators they own before abandoning them, without regard for whether those enumerators would care. – supercat Oct 13 '17 at 16:14
  • IMO, `IEnumerator` should have never implemented `IEnumerator` in the first place because because existing code would call `IEnumerable.GetEnumerator()` and receive an `IEnumerator` when it is only written to handle `IEnumerator`. I still think separating the “inner” consumption API from the “outer” lifetime management stuff is preferable, but I guess it works well enough for synchronous scenarios and in-memory collections. I’m trying to solve my async problems by separating lifetime and “consumer” APIs in places, but to do the same with enumerable I would need to invent a whole new API. – binki Oct 13 '17 at 16:21
  • @binki: What obligations would code have when receiving an `IEnumerable` that it wouldn't have when receiving an `IEnumerable`? The fact that `IEnumerator` implements `IDisposable` doesn't impose any new obligations on code which calls `IEnumerable.GetEnumerator`. Code that calls `IEnumerable.GetEnumerator` must--for correctness--be prepared to call `Dispose` on any returned instance that implements `IDisposable`. Having `IEnumerator` implement `IDisposable` doesn't add any new obligations--it simply makes it easier to honor the obligations `IEnumerable` already has. – supercat Oct 13 '17 at 16:59
  • I do not see any mention of `Dispose` in the docs for [`IEnumerator.GetEnumerator()`](https://msdn.microsoft.com/en-us/library/system.collections.ienumerable.getenumerator(v=vs.110).aspx). Where is it specified that the caller is obligated to `using (nonGenericEnumerator as IDisposable) {}`? I myself consider the fact that `IEnumerator.GetEnumerator()`’s return type is not `IDisposable` an indication to the caller that the caller **should not** call `Dispose()` on it. The C# compiler [agrees with that inference](https://gist.github.com/binki/7d99fa49465257670733e91ed94dcaa7). – binki Oct 13 '17 at 17:09
  • 2
    @binki: The C# code for `foreach` calls `IDisposable` if the instance returned by `GetEnumerator` implements it. – supercat Oct 13 '17 at 17:29
4

IDispoable being a very common interface, there's no harm having your interface inheriting from it. You will so avoid type checking in your code at the only cost to have a no-op implementation in some of your ISample implementations. So your 2nd choice might be better from this point of view.

Seb
  • 2,637
  • 1
  • 17
  • 15
  • That's simply not true. If your class has a field that implements IDisposable that class has to be IDisposable as well to call the Dispose of the field. If you add IDisposable when it is not needed you end up with half of your classes being IDisposable. – Ignacio Soler Garcia Mar 08 '16 at 08:24
4

I am starting to think that putting IDisposable on an interface can cause some problems. It implies that the lifetime of all objects implementing that interface can be safely synchronously ended. I.e., it allows anyone to write code like this and requires all implementations to support IDisposable:

using (ISample myInstance = GetISampleInstance())
{
    myInstance.DoSomething();
}

Only the code which is accessing the concrete type can know the right way to control the lifetime of the object. For example, a type might not need disposal in the first place, it might support IDisposable, or it might require awaiting some asynchronous cleanup process after you are done using it (e.g., something like option 2 here).

An interface author cannot predict all the possible future lifetime/scope management needs of implementing classes. The purpose of an interface is to allow an object to expose some API so that it can be made useful to some consumer. Some interfaces may be related to lifetime management (such as IDisposable itself), but mixing them with interfaces unrelated to lifetime management can make writing an implementation of the interface hard or impossible. If you have very few implementations of your interface and structure your code so that the interface’s consumer and lifetime/scope-manager are in the same method, then this distinction is not clear at first. But if you start passing your object around, this will be clearer.

void ConsumeSample(ISample sample)
{
    // INCORRECT CODE!
    // It is a developer mistake to write “using” in consumer code.
    // “using” should only be used by the code that is managing the lifetime.
    using (sample)
    {
        sample.DoSomething();
    }

    // CORRECT CODE
    sample.DoSomething();
}

async Task ManageObjectLifetimesAsync()
{
    SampleB sampleB = new SampleB();
    using (SampleA sampleA = new SampleA())
    {
        DoSomething(sampleA);
        DoSomething(sampleB);
        DoSomething(sampleA);
    }

    DoSomething(sampleB);

    // In the future you may have an implementation of ISample
    // which requires a completely different type of lifetime
    // management than IDisposable:
    SampleC = new SampleC();
    try
    {
        DoSomething(sampleC);
    }
    finally
    {
        sampleC.Complete();
        await sampleC.Completion;
    }
}

class SampleC : ISample
{
    public void Complete();
    public Task Completion { get; }
}

In the code sample above, I demonstrated three types of lifetime management scenarios, adding to the two you provided.

  1. SampleA is IDisposable with synchronous using () {} support.
  2. SampleB uses pure garbage collection (it does not consume any resources).
  3. SampleC uses resources which prevent it from being synchronously disposed and requires an await at the end of its lifetime (so that it may notify the lifetime management code that it is done consuming resources and bubble up any asynchronously encountered exceptions).

By keeping lifetime management separate from your other interfaces, you can prevent developer mistakes (e.g., accidental calls to Dispose()) and more cleanly support future unanticipated lifetime/scope management patterns.

binki
  • 7,754
  • 5
  • 64
  • 110
  • 5
    Types that don't require cleanup can implement `IDisposable` easily and correctly via `void IDisposable.Dispose() {};`. If some objects returned from a factory function will need cleanup, it's easier and simpler to have it return a type which implements `IDisposable` and require that clients balance every call to the factory function with a call to a returned object's `Dispose` than to require that clients determine which objects need cleanup. – supercat Oct 13 '17 at 16:18
1

Personally I would choose 1, unless you make a concrete example for two. A good example of two is an IList.

An IList means you need to implement an indexer for your collection. However, an IList really also means you are an IEnumerable, and you should have a GetEnumerator() for your class.

In your case you are hesistant that classes that implement ISample would need to implement IDisposable, if not every class that implements your interface has to implement IDisposable then don't force them to.

Focusing on IDispoable specifically, IDispoable in particular forces programmers using your class to write some reasonably ugly code. For example,

foreach(item in IEnumerable<ISample> items)
{
    try
    {
        // Do stuff with item
    }
    finally
    {
        IDisposable amIDisposable = item as IDisposable;
        if(amIDisposable != null)
            amIDisposable.Dispose();  
    }
}

Not only is the code horrible, there will be a significant performance penalty in ensuring there is a finally block to dispose of the item for every iteration of that list, even if Dispose() just returns in the implementation.

Pasted the code to answer one of the comments in here, easier to read.

M Afifi
  • 4,645
  • 2
  • 28
  • 48
  • But what if there are factories (ore simple usage of IoC-Container) that delivers implementation objects with or without the requirement of disposing them? You'll have to call Dispose() by casting and calling it explicitely. This way your code needs knowledge about the (possible) implementation... some kind of coupling? Otherwise you could just use a using block which is very easy. – Beachwalker May 09 '12 at 11:19
  • @Stegi using block might not look ugly, but it will still have the performance penalty. Also I don't see how you'd use a using block inside of a foreach for example. Microsoft's code is litered with examples where it does this, IDisposable amIDisposable = object as IDisposable; if(amIDisposable != null) amIDisposable.Dispose(); Because as does not throw an exception if it can not cast it to an IDisposable, the performance penalty is near none existant. – M Afifi May 09 '12 at 13:24
  • Even if only 1% of classes which implement or inherit from some particular type would do anything in `IDisposable.Dispose`, if it's ever going to be necessary to call `IDisposable.Dispose` on a variable or field declared as that type (as opposed to one of an implementing or derived type) that's a good sign that the type itself should inherit or implement `IDisposable`. Testing at run-time whether an object instance implements `IDisposable` and calling `IDisposable.Dispose` on it if so (as was necessary with non-generic `IEnumerable`) is a major code smell. – supercat May 09 '12 at 20:39