在 Unity 中的程序集之间共享实例
Sharing instance between assemblies in Unity
目前我正在使用单例class,它在我使用它的每个地方都工作正常。但是我想为此使用 Unity IoC 容器。按如下方式注册它并不能按照我希望的方式进行:
container.RegisterInstance(new SomeNiceClass(), new ContainerControlledLifetimeManager());
意味着如果我这样做:
container.Resolve<SomeNiceClass>();
在一个 class 程序集中,除了首次注册之外,我将获得一个新实例。
只要您传递对同一容器的引用,程序集边界就无关紧要。
您可以尝试命名它:
container.RegisterInstance<SomeNiceClass>("MySingleton", new SomeNiceClass(), new ContainerControlledLifetimeManager());
container.Resolve<SomeNiceClass>("MySingleton");
还值得注意的是,文档说容器生命周期是 RegisterInstance
的默认值,因此您可以像这样注册:
container.RegisterInstance<SomeNiceClass>(new SomeNiceClass());
在此处阅读 MSDN 对此的评论:https://msdn.microsoft.com/en-ca/library/ff647854.aspx
如果你还有问题,你需要再看看你是如何处理容器的。
正如您自己注意到的,您需要使用同一容器实例。
您可以通过 Unity 的 IModule
界面执行此操作。
[Module(ModuleName="MyExternalModule", OnDemand=false)]
public class MyExternalModule : IModule
{
private readonly IUnityContainer container;
public MyExternalModule(IUnityContainer container)
{
if (container == null)
throw new ArgumentNullException("container");
this.container = container;
}
public void Initialize()
{
container.RegisterInstance(new MyService());
}
}
在您的应用程序中,您注册了模块:
public class ApplicationBootstrapper : UnityBootstrapper
{
protected override IModuleCatalog CreateModuleCatalog()
{
var moduleCatalog = new ModuleCatalog();
moduleCatalog.AddModule(typeof(YourCompany.MyModule.MyExternalModule), InitializationMode.WhenAvailable);
return moduleCatalog;
}
...
}
编辑:
作为附加说明,您的模块 不应 实例化它们自己的容器。容器只需在 主应用程序 中实例化。模块只做注册,仅此而已!
如果您需要模块中的容器实例(在 IModule
实现之外),您只需在构造函数中声明它。
public class MyModuleResolver
{
private readonly IUnityContainer container;
public MyModuleResolver(IUnityContainer container)
{
if(container == null)
{
throw new ArgumentNullException("container");
}
this.container = container;
}
}
但是,请注意,在 domain/business 层内直接引用容器并不是一个好习惯。您必须自己使用 IUnityContainer
的原因只有两个:
- 在你的引导程序中
- 在您的应用程序类型中,即需要根据运行时参数手动解析特定类型的某些工厂类
其他一切都应该通过注入从您的容器中自动完成。
我遇到了完全相同的问题(不确定为什么每个人都评论说重现如此困难或不可能 - 据我所知,这很简单。
仅供参考,这里是重现的步骤:
- 创建包含 2 个或更多程序集的解决方案。
- 在每个组件中放置一个或多个模块。
- 与其让引导程序硬编码加载每个程序集,不如使用一些动态发现和加载它们的方法。我使用了这个的简化版本:
http://brianlagunas.com/prism-dynamically-discover-and-load-modules-at-runtime/
- 尝试在一个模块中注册一些单例 (ContainerControlledLifetime) 类型或实例。
- 也尝试在其他模块中引用这个单例。
- 在我的例子中,我还乱序预加载了第一个 "main" 模块,在引导程序的 ConfigureContainer 部分 - 也许这是导致我出现问题的部分原因。
一旦所有这些都到位,我重现了同样的问题 - assembly1 中的模块(与主模块相同)以某种方式获得了我的 "singleton" 的实例 1,而 assembly2 中的模块(仅稍后加载,动态)得到了我的单身人士的实例 2。我确认他们使用的是同一个容器实例。
我通过将这些单例的注册完全移出模块并在加载任何模块之前移入我的引导程序(再次在 ConfigureContainer 中)解决了我的问题。
另请注意 - 当我使用 assembly2 中的模块显式调用 ModuleCatalog.AddModule(不更改任何其余代码)时,多实例问题消失了。但是,当我删除显式加载它的调用(在 ConfigureModuleCatalog 中),而只是允许我的动态模块目录动态加载它时,问题就出现了。我想这只是为了在引导程序中按照正确的事件顺序获取内容 - 但在这种情况下,它肯定不会像您预期的那样工作。
抱歉,如果这不是一个非常简洁的解决方案 - 解决所有这些问题需要大量的反复试验。但我希望这对某人有所帮助。
** 更新 **
在写完所有这些文章之后,再仔细考虑一下……我终于意识到是什么原因造成的。本质上,因为我预加载了主模块,它也恰好注册了一些单例,然后 Unity 在引导程序模块初始化阶段重新加载了同一个模块以及所有其他模块,同一个模块实际上是有效的重新注册那些单例 类,我猜这会导致重复定义并最终创建另一个实例。我错误地认为 Unity 足够聪明,知道它已经加载了模块,而不是重新加载它。
我已经向我的模块(使用基本模块)添加了一些功能,以仅允许每个模块加载一次,并忽略对相同模块类型(静态)的任何后续调用 Initialize。然后我将所有单例定义移回模块中。现在一切似乎都很好。所以 - 这就是我的特殊情况。
所以回到最初发布这个问题的人 - 以及任何其他寻找答案的人 - 如果你看到你的单身人士在 Unity 中实例化不止一次,也许仔细检查你没有加载模块或注册类型不止一次。
目前我正在使用单例class,它在我使用它的每个地方都工作正常。但是我想为此使用 Unity IoC 容器。按如下方式注册它并不能按照我希望的方式进行:
container.RegisterInstance(new SomeNiceClass(), new ContainerControlledLifetimeManager());
意味着如果我这样做:
container.Resolve<SomeNiceClass>();
在一个 class 程序集中,除了首次注册之外,我将获得一个新实例。
只要您传递对同一容器的引用,程序集边界就无关紧要。
您可以尝试命名它:
container.RegisterInstance<SomeNiceClass>("MySingleton", new SomeNiceClass(), new ContainerControlledLifetimeManager());
container.Resolve<SomeNiceClass>("MySingleton");
还值得注意的是,文档说容器生命周期是 RegisterInstance
的默认值,因此您可以像这样注册:
container.RegisterInstance<SomeNiceClass>(new SomeNiceClass());
在此处阅读 MSDN 对此的评论:https://msdn.microsoft.com/en-ca/library/ff647854.aspx
如果你还有问题,你需要再看看你是如何处理容器的。
正如您自己注意到的,您需要使用同一容器实例。
您可以通过 Unity 的 IModule
界面执行此操作。
[Module(ModuleName="MyExternalModule", OnDemand=false)]
public class MyExternalModule : IModule
{
private readonly IUnityContainer container;
public MyExternalModule(IUnityContainer container)
{
if (container == null)
throw new ArgumentNullException("container");
this.container = container;
}
public void Initialize()
{
container.RegisterInstance(new MyService());
}
}
在您的应用程序中,您注册了模块:
public class ApplicationBootstrapper : UnityBootstrapper
{
protected override IModuleCatalog CreateModuleCatalog()
{
var moduleCatalog = new ModuleCatalog();
moduleCatalog.AddModule(typeof(YourCompany.MyModule.MyExternalModule), InitializationMode.WhenAvailable);
return moduleCatalog;
}
...
}
编辑: 作为附加说明,您的模块 不应 实例化它们自己的容器。容器只需在 主应用程序 中实例化。模块只做注册,仅此而已!
如果您需要模块中的容器实例(在 IModule
实现之外),您只需在构造函数中声明它。
public class MyModuleResolver
{
private readonly IUnityContainer container;
public MyModuleResolver(IUnityContainer container)
{
if(container == null)
{
throw new ArgumentNullException("container");
}
this.container = container;
}
}
但是,请注意,在 domain/business 层内直接引用容器并不是一个好习惯。您必须自己使用 IUnityContainer
的原因只有两个:
- 在你的引导程序中
- 在您的应用程序类型中,即需要根据运行时参数手动解析特定类型的某些工厂类
其他一切都应该通过注入从您的容器中自动完成。
我遇到了完全相同的问题(不确定为什么每个人都评论说重现如此困难或不可能 - 据我所知,这很简单。
仅供参考,这里是重现的步骤:
- 创建包含 2 个或更多程序集的解决方案。
- 在每个组件中放置一个或多个模块。
- 与其让引导程序硬编码加载每个程序集,不如使用一些动态发现和加载它们的方法。我使用了这个的简化版本: http://brianlagunas.com/prism-dynamically-discover-and-load-modules-at-runtime/
- 尝试在一个模块中注册一些单例 (ContainerControlledLifetime) 类型或实例。
- 也尝试在其他模块中引用这个单例。
- 在我的例子中,我还乱序预加载了第一个 "main" 模块,在引导程序的 ConfigureContainer 部分 - 也许这是导致我出现问题的部分原因。
一旦所有这些都到位,我重现了同样的问题 - assembly1 中的模块(与主模块相同)以某种方式获得了我的 "singleton" 的实例 1,而 assembly2 中的模块(仅稍后加载,动态)得到了我的单身人士的实例 2。我确认他们使用的是同一个容器实例。
我通过将这些单例的注册完全移出模块并在加载任何模块之前移入我的引导程序(再次在 ConfigureContainer 中)解决了我的问题。
另请注意 - 当我使用 assembly2 中的模块显式调用 ModuleCatalog.AddModule(不更改任何其余代码)时,多实例问题消失了。但是,当我删除显式加载它的调用(在 ConfigureModuleCatalog 中),而只是允许我的动态模块目录动态加载它时,问题就出现了。我想这只是为了在引导程序中按照正确的事件顺序获取内容 - 但在这种情况下,它肯定不会像您预期的那样工作。
抱歉,如果这不是一个非常简洁的解决方案 - 解决所有这些问题需要大量的反复试验。但我希望这对某人有所帮助。
** 更新 **
在写完所有这些文章之后,再仔细考虑一下……我终于意识到是什么原因造成的。本质上,因为我预加载了主模块,它也恰好注册了一些单例,然后 Unity 在引导程序模块初始化阶段重新加载了同一个模块以及所有其他模块,同一个模块实际上是有效的重新注册那些单例 类,我猜这会导致重复定义并最终创建另一个实例。我错误地认为 Unity 足够聪明,知道它已经加载了模块,而不是重新加载它。
我已经向我的模块(使用基本模块)添加了一些功能,以仅允许每个模块加载一次,并忽略对相同模块类型(静态)的任何后续调用 Initialize。然后我将所有单例定义移回模块中。现在一切似乎都很好。所以 - 这就是我的特殊情况。
所以回到最初发布这个问题的人 - 以及任何其他寻找答案的人 - 如果你看到你的单身人士在 Unity 中实例化不止一次,也许仔细检查你没有加载模块或注册类型不止一次。