59

从以下情况开始:

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
}

SampleA 类应该实现接口 IDisposable 来释放资源。您可以通过两种方式解决此问题:

1.在类SampleA中添加需要的接口:

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

2.将其添加到接口ISample并强制派生类实现它:

public interface ISample : IDisposable
{
}

如果将其放入接口中,则强制任何实现都实现 IDisposable,即使它们没有可处置的东西。另一方面,很清楚地看到接口的具体实现需要一个 dispose/using 块,并且您不需要强制转换为 IDisposable 进行清理。两种方式都可能有更多的优点/缺点......你为什么建议使用一种优于另一种的方式?

4

7 回答 7

25

遵循SOLID的接口隔离原则,如果您将 IDisposable 添加到接口,您将向不感兴趣的客户端提供方法,因此您应该将其添加到 A。

除此之外,接口永远不是一次性的,因为一次性与接口的具体实现有关,与接口本身无关。

任何接口都可能在有或没有需要处理的元素的情况下实现。

于 2014-10-17T09:31:56.833 回答
14

如果您将using(){}模式应用到所有界面,最好ISample从中派生,IDisposable因为设计界面时的经验法则是支持“易于使用”而不是“易于实现”

于 2012-05-09T10:58:29.353 回答
7

就个人而言,如果 allISample应该是一次性的,我会把它放在界面上,如果只有一些,我只会把它放在应该放在的类上。

听起来你有后一种情况。

于 2012-05-09T10:10:28.103 回答
6

如果至少有一些实现可能会实现一个接口,那么接口IFoo可能应该实现,并且至少在某些情况下,对实例的最后一个幸存引用将存储在类型的变量或字段中。如果任何实现可以实现并且实例将通过工厂接口创建(就像 的实例一样,在许多情况下是通过工厂接口创建的),它几乎肯定应该实现。IDisposableIDisposableIFooIDisposableIDisposableIEnumerator<T>IEnumerable<T>

比较IEnumerable<T>IEnumerator<T>指导意义。一些实现了的类型IEnumerable<T>也实现IDisposable了,但是创建这种类型实例的代码会知道它们是什么,知道它们需要处理,并将它们用作它们的特定类型。这样的实例可以作为 type 传递给其他例程IEnumerable<T>,而那些其他例程将不知道对象最终需要处理,但在大多数情况下,那些其他例程不会是最后一个保存对对象的引用的例程。相比之下, 的实例IEnumerator<T>通常是由代码创建、使用并最终放弃的,这些代码对这些实例的底层类型一无所知,除了它们是由IEnumerable<T>. 的一些实现IEnumerable<T>.GetEnumerator()如果在放弃之前没有调用它们的方法,则return 的实现IEnumerator<T>将泄漏资源,并且大多数接受类型参数的代码将无法知道是否可以将此类类型传递给它。尽管可以包含一个属性来指示是否必须释放返回的对象,或者只是要求调用的例程检查返回对象的类型以查看它是否实现,但无条件调用方法会更快更容易这可能不会做任何事情,而不是确定是否有必要并仅在必要时调用它。IDisposable.DisposeIEnumerable<T>IEnumerable<T>EnumeratorTypeNeedsDisposalIEnumerator<T>GetEnumerator()IDisposableDisposeDispose

于 2012-05-09T15:17:02.100 回答
4

IDispoable 是一个非常常见的接口,让你的接口继承它并没有什么坏处。您将因此避免在代码中进行类型检查,唯一的代价是在您的某些 ISample 实现中实现无操作实现。因此,从这个角度来看,您的第二个选择可能会更好。

于 2012-05-09T10:53:13.177 回答
4

我开始认为放置IDisposable接口会导致一些问题。这意味着实现该接口的所有对象的生命周期都可以安全地同步结束。即,它允许任何人编写这样的代码,并且需要所有实现来支持IDisposable

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

只有访问具体类型的代码才能知道控制对象生命周期的正确方法。例如,一个类型可能一开始就不需要处理,它可能支持IDisposable,或者在您使用完它之后可能需要awaiting一些异步清理过程(例如,此处的选项 2 之类的东西)。

接口作者无法预测实现类的所有可能的未来生命周期/范围管理需求。接口的目的是允许对象公开一些 API,以便它可以对某些消费者有用。一些接口可能与生命周期管理相关(例如其IDisposable自身),但是将它们与与生命周期管理无关的接口混合可能会使编写接口的实现变得困难或不可能。如果您的接口实现很少,并且您的代码结构使得接口的使用者和生命周期/范围管理器在同一个方法中,那么这种区别一开始并不明确。但是如果你开始传递你的对象,这会更清楚。

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; }
}

在上面的代码示例中,我演示了三种类型的生命周期管理场景,添加到您提供的两种。

  1. SampleA具有IDisposable同步using () {}支持。
  2. SampleB使用纯垃圾收集(它不消耗任何资源)。
  3. SampleC使用阻止它被同步处理的资源,并await在其生命周期结束时需要一个(以便它可以通知生命周期管理代码它已完成消耗资源并冒泡任何异步遇到的异常)。

通过将生命周期管理与您的其他接口分开,您可以防止开发人员错误(例如,意外调用Dispose())并更清晰地支持未来未预料到的生命周期/范围管理模式。

于 2017-10-13T15:21:38.883 回答
1

就我个人而言,我会选择 1,除非你为两个做一个具体的例子。两个的一个很好的例子是IList.

IList意味着您需要为您的集合实现一个索引器。但是,anIList实际上也意味着您是IEnumerable,并且您的班级应该有 a GetEnumerator()

在您的情况下,您对实现的类ISample是否需要实现犹豫不决IDisposable,如果不是每个实现您的接口的类都必须实现,IDisposable那么不要强迫它们。

特别关注IDispoableIDispoable特别是迫使程序员使用你的类来编写一些相当丑陋的代码。例如,

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

不仅代码很糟糕,而且在确保有一个 finally 块来处理该列表的每次迭代的项目时,即使Dispose()只是在实现中返回,也会有显着的性能损失。

粘贴代码以回答此处的评论之一,更易于阅读。

于 2012-05-09T10:10:55.787 回答