7

我有一个问题,我要标记这个主观的,因为这就是我认为它演变成的,更多的讨论。我希望有一些好的想法或一些发人深省的东西。对于这个冗长的问题,我深表歉意,但您需要了解上下文。

问题基本上是:

  • 您如何处理与 IoC 容器相关的具体类型?具体来说,谁负责处理它们,如果它们需要处理,以及这些知识如何传播到调用代码?

您是否要求它们是 IDisposable 的?如果不是,那么该代码是面向未来的,还是您不能使用一次性物品的规则?如果您对接口和具体类型强制执行 IDisposable 要求以保证未来的发展,那么作为构造函数调用的一部分注入的对象是谁的责任?


编辑:我接受了@Chris Ballard的答案,因为它与我们最终采用的方法最接近。

基本上,我们总是返回一个看起来像这样的类型:

public interface IService<T> : IDisposable
    where T: class
{
    T Instance { get; }
    Boolean Success { get; }
    String FailureMessage { get; } // in case Success=false
}

然后我们从 .Resolve 和 .TryResolve 返回一个实现这个接口的对象,这样我们在调用代码中得到的总是相同的类型。

现在,实现此接口的对象IService<T>是 IDisposable 的,并且应该始终被释放。解决服务的程序员不能决定是否IService<T>应该释放对象。

然而,这是关键部分,无论服务实例是否应该被释放,这些知识都被嵌入到实现的对象IService<T>中,所以如果它是一个工厂范围的服务(即每次调用 Resolve 最终都会得到一个新的服务实例),那么服务实例会在IService<T>对象被释放的时候被释放。

这也使得支持其他特殊范围成为可能,例如池。我们现在可以说我们需要最少 2 个服务实例,最多 15 个,通常是 5 个,这意味着每次调用 .Resolve 都会从可用对象池中检索一个服务实例,或者构造一个新实例。然后,当IService<T>持有池化服务的对象被释放时,服务实例被释放回其池中。

当然,这使所有代码看起来像这样:

using (var service = ServiceContainer.Global.Resolve<ISomeService>())
{
    service.Instance.DoSomething();
}

但这是一种干净的方法,并且无论使用的服务类型或具体对象如何,它都具有相同的语法,因此我们选择它作为可接受的解决方案。


原始问题如下,供后代使用


冗长的问题来了:

我们有一个我们使用的 IoC 容器,最近我们发现了问题所在。

在非 IoC 代码中,当我们想使用文件时,我们使用了这样的类:

using (Stream stream = new FileStream(...))
{
    ...
}

毫无疑问,该类是否具有有限资源,因为我们知道文件必须关闭,并且该类本身实现了 IDisposable。规则很简单,我们构造一个对象的每个类,实现 IDisposable,都必须被处理掉。无话可问。调用 Dispose 是否是可选的不是由此类的用户决定的。

好的,接下来就是迈向 IoC 容器的第一步。假设我们不希望代码直接与文件对话,而是通过一层间接。让我们将此类称为此示例的 BinaryDataProvider。在内部,该类正在使用一个流,它仍然是一个一次性对象,因此上面的代码将更改为:

using (BinaryDataProvider provider = new BinaryDataProvider(...))
{
    ...
}

这变化不大。类实现IDisposable的知识还在,不问,需要调用Dispose。

但是,让我们假设我们的类提供的数据现在不使用任何有限的资源。

那么上面的代码可以写成:

BinaryDataProvider provider = new BinaryDataProvider();
...

好的,到目前为止一切都很好,但问题的关键来了。假设我们想要使用 IoC 容器来注入此提供程序,而不是依赖于特定的具体类型。

代码将是:

IBinaryDataProvider provider =
    ServiceContainer.Global.Resolve<IBinaryDataProvider>();
...

请注意,我假设有一个独立的接口可用,我们可以通过它访问对象。

有了上面的改动,如果我们以后想要使用一个真正应该被处理掉的对象怎么办?解析该接口的现有代码都没有被编写来处理该对象,那么现在怎么办?

在我们看来,我们必须选择一种解决方案:

  • 实现运行时检查,检查是否正在注册的具体类型实现 IDisposable,要求它公开的接口也实现 IDisposable。这不是一个好的解决方案
  • 在对正在使用的接口进行约束之前,它们必须始终从 IDisposable 继承,以便面向未来
  • 强制运行时没有具体类型可以是 IDisposable,因为这不是由使用 IoC 容器的代码专门处理的
  • 只需让程序员检查对象是否实现 IDisposable 并“做正确的事”?
  • 还有其他人吗?

另外,在构造函数中注入对象呢?我们的容器以及我们研究过的其他一些容器能够将新对象注入到具体类型的构造函数的参数中。例如,如果我们BinaryDataProvider需要一个实现ILogging接口的对象,如果我们对这些对象强制执行 IDispose-“能力”,那么处理日志对象的责任是谁的?

你怎么看?我想要意见,好的和坏的。

4

3 回答 3

3

一种选择可能是使用工厂模式,这样由 IoC 容器直接创建的对象永远不需要自己处理,例如

IBinaryDataProviderFactory factory =
    ServiceContainer.Global.Resolve<IBinaryDataProviderFactory>();
using(IBinaryDataProvider provider = factory.CreateProvider())
{
    ...
}

缺点是增加了复杂性,但这确实意味着容器永远不会创建开发人员应该处理的任何东西——它总是显式的代码来做这件事。

如果您真的想让它变得明显,可以将工厂方法命名为 CreateDisposableProvider() 之类的名称。

于 2009-02-17T13:08:36.767 回答
2

(免责声明:我是根据 java 的东西来回答这个问题的。虽然我编写了 C#,但我没有在 C# 中代理任何东西,但我知道这是可能的。对 java 术语感到抱歉)

您可以让 IoC 框架检查正在构造的对象以查看它是否支持 IDisposable。如果没有,您可以使用动态代理来包装 IoC 框架提供给客户端代码的实际对象。此动态代理可以实现 IDisposable,因此您始终将 IDisposable 传递给客户端。只要您使用的接口应该相当简单?

然后,对象是 IDisposable 时,您将遇到与开发人员通信的问题。我不太确定如何以一种好的方式完成这项工作。

于 2009-02-17T20:15:06.057 回答
1

您实际上想出了一个非常肮脏的解决方案:您的IService合同违反了SRP,这是一个很大的禁忌。

我建议将所谓的“单例”服务与所谓的“原型”服务区分开来。“单例”的生命周期由容器管理,容器可以在运行时查询特定实例是否实现IDisposable,如果是,则在关闭时调用Dispose()

另一方面,管理原型完全是调用代码的责任。

于 2009-04-27T11:48:21.783 回答