我开始觉得在接口上放置IDisposable
可能会引起一些问题。这意味着实现该接口的所有对象的生命周期都可以被安全同步地结束。也就是说,它允许任何人编写下面这样的代码,并要求所有的实现支持IDisposable
:
using (ISample myInstance = GetISampleInstance())
{
myInstance.DoSomething();
}
只有访问具体类型的代码才能知道控制对象生命周期的正确方法。例如,某个类型可能根本不需要处理,它可能支持IDisposable
,或者在使用完毕后可能需要等待某些异步清理过程(例如这里的选项2)。
接口作者无法预测实现类所有可能的未来生命周期/作用域管理需求。接口的目的是允许对象公开一些API,以便使其对某些使用者有用。一些接口可能与生命周期管理相关(例如IDisposable
本身),但将其与与生命周期管理无关的接口混合在一起可能会使编写接口实现变得困难或不可能。如果您只有很少的接口实现,并且结构化代码使接口的使用者和生命周期/作用域管理器在同一个方法中,那么此区别一开始可能并不明显。但是,如果您开始传递对象,这一点会更加清晰明了。
void ConsumeSample(ISample sample)
{
using (sample)
{
sample.DoSomething();
}
sample.DoSomething();
}
async Task ManageObjectLifetimesAsync()
{
SampleB sampleB = new SampleB();
using (SampleA sampleA = new SampleA())
{
DoSomething(sampleA);
DoSomething(sampleB);
DoSomething(sampleA);
}
DoSomething(sampleB);
SampleC = new SampleC();
try
{
DoSomething(sampleC);
}
finally
{
sampleC.Complete();
await sampleC.Completion;
}
}
class SampleC : ISample
{
public void Complete();
public Task Completion { get; }
}
在上面的代码示例中,我展示了三种生命周期管理场景,加上你提供的两种。
1.
SampleA
是支持同步
using () {}
的
IDisposable
。
2.
SampleB
使用纯垃圾回收(它不消耗任何资源)。
3.
SampleC
使用资源,阻止其被同步处理,并需要在其生命周期结束时使用
await
(以便通知生命周期管理代码它已完成资源消耗并冒泡任何异步遇到的异常)。
通过将生命周期管理与其他接口分开,您可以防止开发人员错误(例如,意外调用
Dispose()
)并更清晰地支持未来未预期的生命周期/范围管理模式。