C# 装潢师和IDisposable
我有一个子类C# 装潢师和IDisposable,c#,decorator,ioc-container,simple-injector,C#,Decorator,Ioc Container,Simple Injector,我有一个子类DbContext public class MyContext : DbContext { } 我有一个IUnitOfWork关于MyContext的抽象,它实现了IDisposable,以确保像MyContext这样的引用在适当的时候被处理掉 public interface IUnitOfWork : IDisposable { } public class UnitOfWork : IUnitOfWork { private readonly MyContext
DbContext
public class MyContext : DbContext { }
我有一个IUnitOfWork
关于MyContext
的抽象,它实现了IDisposable
,以确保像MyContext
这样的引用在适当的时候被处理掉
public interface IUnitOfWork : IDisposable { }
public class UnitOfWork : IUnitOfWork
{
private readonly MyContext _context;
public UnitOfWork()
{
_context = new MyContext();
}
~UnitOfWork()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private bool _disposed;
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
if (_context != null) _context.Dispose();
}
_disposed = true;
}
}
MyUnitOfWork
以每个(web)请求的生存期范围注册。我有IUnitOfWork
的装饰器,它们可以注册为瞬态或生命周期范围,我的问题是它们应该如何实现IDisposable
——具体是应该还是不应该传递对Dispose()
的调用
我看到两个选项(我猜选项2是正确的答案):
Dispose()
。如果它是生命周期范围的,则应该Dispose()
的调用。对象应该只封装实例的Dispose()
,并且修饰不是封装就我个人而言,我怀疑你需要逐案处理这件事。一些装饰师可能有充分的理由理解范围界定;对于大多数人来说,简单地传递它可能是一个很好的默认值。很少有人不应该明确地处理这个链——我所看到的主要情况是,它专门用来抵消另一个应该考虑范围界定的装饰者:没有(总是处理)
作为一个相关的例子-考虑诸如<代码> GZIPStase之类的东西,对于大多数人来说,它们只处理一个逻辑块——因此默认为“处理流”是好的;但是这个决定可以通过一个让你告诉它如何行动的。在带有可选参数的C#的最新版本中,这可以在单个构造函数中完成
选项2有问题,因为它要求您(或容器)跟踪所有中间对象;如果您的容器可以方便地这样做,那么很好-但也要注意,它们必须按照正确的顺序进行处理(从外到内)。因为在decorator链中,可能会有一些挂起的操作-计划在请求时向下游刷新,或者(作为最后手段)在dispose期间刷新 对于实现IDisposable,[装饰者]应该做些什么 这又回到了所有权的一般原则。问问自己:“谁拥有那种一次性的?”。这个问题的答案是:拥有这个类型的人负责处理它 由于一次性类型是从外部传递给装饰者的,装饰者没有创建该类型,通常不应该负责清理它。decorator无法知道是否应该处理该类型(因为它不控制其生存期),这在您的案例中非常清楚,因为decorator可以注册为transient,而decoratee的生存期要长得多。在您的情况下,如果您从decorator中处理decoree,您的系统将简单地中断 因此,装饰者永远不应该处置被装饰者,仅仅因为它不拥有被装饰者。处理那个被装饰者是你的责任。在这种情况下,我们谈论的是装饰师并不重要;它仍然可以归结为所有权的一般原则 每个装潢师应该只关心自己的处理,并且应该 永远不要将调用传递给装饰实例 对。尽管装饰程序应该处理它拥有的所有东西,但是由于您使用的是依赖项注入,它本身通常不会创建太多东西,因此不拥有这些东西 另一方面,您的UnitOfWork
创建了一个新的MyContext
类,因此拥有该实例的所有权,它应该处理掉它
这条规则也有例外,但归根结底还是所有权的问题。有时您确实会将类型的所有权传递给其他人。例如,当使用工厂方法时,按照约定,工厂方法将所创建对象的所有权传递给调用方。有时所有权会传递给创建的对象,例如.NET的StreamReader
类。API文档对此很清楚,但由于设计非常不直观,开发人员一直被这种行为绊倒。NET framework中的大多数类型都不是这样工作的。例如,SqlCommand
类没有处理SqlConnection
,如果它确实处理了连接,那将非常恼人
另一种看待这个问题的方式是从政治的角度。通过让IUnitOfWork
实现IDisposable
您违反了,因为“抽象不应该依赖于细节;细节应该依赖于抽象”。通过实现IDisposable
您正在将实现细节泄漏到IUnitOfWork
接口中。实现IDisposable
意味着该类具有需要处理的非托管资源,例如文件句柄和连接字符串。这些都是实现细节,因为这种接口的每个实现实际上都不可能需要处理。您只需要为您的单元测试创建一个假的或模拟的实现,并且您有一个不需要处理的实现的证明
因此,当您通过从IUnitOfWork
中删除IDisposable
接口并将其移动到实现来修复此DIP冲突时,装饰程序将无法处理装饰对象,因为它无法知道
public class UnitOfWorkDecorator : IUnitOfWork
{
private readonly IUnitOfWork _decorated;
public UnitOfWorkDecorator(IUnitOfWork decorated)
{
_decorated = decorated;
}
public void Dispose()
{
//do we pass on the call?
_decorated.Dispose();
}
}
public sealed class UnitOfWork : IUnitOfWork, IDisposable
{
private readonly MyContext _context;
public UnitOfWork()
{
_context = new MyContext();
}
public void Dispose()
{
_context.Dispose();
}
}
public sealed class UnitOfWork : IUnitOfWork
{
private readonly MyContext _context;
public UnitOfWork(MyContext context)
{
_context = context;
}
}