我开始认为放置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; }
}
在上面的代码示例中,我演示了三种类型的生命周期管理场景,添加到您提供的两种。
SampleA
具有IDisposable
同步using () {}
支持。
SampleB
使用纯垃圾收集(它不消耗任何资源)。
SampleC
使用阻止它被同步处理的资源,并await
在其生命周期结束时需要一个(以便它可以通知生命周期管理代码它已完成消耗资源并冒泡任何异步遇到的异常)。
通过将生命周期管理与您的其他接口分开,您可以防止开发人员错误(例如,意外调用Dispose()
)并更清晰地支持未来未预料到的生命周期/范围管理模式。