Starting from the follwing 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?
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.
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.
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, anIList
really also means you are anIEnumerable
, and you should have aGetEnumerator()
for your class.In your case you are hesistant that classes that implement
ISample
would need to implementIDisposable
, if not every class that implements your interface has to implementIDisposable
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,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.
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 supportIDisposable
: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 requireawaiting
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.In the code sample above, I demonstrated three types of lifetime management scenarios, adding to the two you provided.
SampleA
isIDisposable
with synchronoususing () {}
support.SampleB
uses pure garbage collection (it does not consume any resources).SampleC
uses resources which prevent it from being synchronously disposed and requires anawait
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.An interface
IFoo
should probably implementIDisposable
if it is likely that at least some some implementations will implementIDisposable
, and on at least some occasions the last surviving reference to an instance will be stored in a variable or field of typeIFoo
. It should almost certainly implementIDisposable
if any implementations might implementIDisposable
and instances will be created via factory interface (as is the case with instances ofIEnumerator<T>
, which in many cases are created via factory interfaceIEnumerable<T>
).Comparing
IEnumerable<T>
andIEnumerator<T>
is instructive. Some types which implementIEnumerable<T>
also implementIDisposable
, 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 typeIEnumerable<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 ofIEnumerator<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 byIEnumerable<T>
. Some implementations ofIEnumerable<T>.GetEnumerator()
return implementations ofIEnumerator<T>
will leak resources if theirIDisposable.Dispose
method is not called before they are abandoned, and most code which accepts parameters of typeIEnumerable<T>
will have no way of knowing if such types may be passed to it. Although it would be possible forIEnumerable<T>
to include a propertyEnumeratorTypeNeedsDisposal
to indicate whether the returnedIEnumerator<T>
would have to be disposed, or simply require that routines which callGetEnumerator()
check the type of the returned object to see if it implementsIDisposable
, it's quicker and easier to unconditionally call aDispose
method that might not do anything, than to determine whetherDispose
is necessary and call it only if so.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.