Declare IDisposable for the class or interface?

后端 未结 7 2225
心在旅途
心在旅途 2020-12-29 01:00

Starting from the follwing situation:

public interface ISample
{
}

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


        
7条回答
  •  梦毁少年i
    2020-12-29 01:29

    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.

提交回复
热议问题