Prism Dependency Injection
1.注册类型(Registering Types)
1.1. Prism中的服务生命周期:
- Transient(瞬态):每次请求服务或类型时,都会获得一个新的实例。
- Singleton(单例):每次请求服务或类型时,都会获得同一个实例。
- Scoped(作用域):在每个容器作用域中获得一个新的实例,但在特定的容器作用域内保持同一个实例。
1.2. Prism中的默认作用域:
- Prism默认不使用作用域,除了在Prism.Maui中,它会为每个页面创建一个作用域。这种作用域用于
INavigationService
、IPageDialogService
和IDialogService
等服务。
1.3. 与ASP.NET Core的比较:
- 如果你熟悉ASP.NET Core,你可能知道三种基本的依赖注册类型:Transients、Singletons和Scoped Services。与Web环境中许多服务围绕用户请求进行作用域划分不同,桌面和移动应用通常只处理单个用户。因此,我们需要决定服务是作为整个应用中重复使用的单个实例,还是每次请求时创建新实例,并在完成后由垃圾回收器清理内存。
1.4. Prism对命名服务注册的要求:
- Prism严格要求使用命名服务注册。这使得Prism能够注册页面以供导航,并在之后根据URI片段(如MyMasterDetailPage/NavigationPage/ViewA)解析它。任何不支持命名服务的依赖注入容器都不能被Prism团队官方实现。
2.注册瞬态(Transient)服务
瞬态服务是指每次请求服务或类型时都会创建一个新的实例。在Prism中,如果你期望每次创建服务时都生成一个新的实例,你可以通过调用Register
方法并提供服务类型和服务实现类型来注册这样的服务。除非在某些情况下,直接注册具体类型(concrete type)可能是合适的。
// Where it will be appropriate to use FooService as a concrete type
containerRegistry.Register<FooService>();
containerRegistry.Register<IBarService, BarService>();
具体来说,代码示例展示了两种注册瞬态服务的方式:
-
如果
FooService
是一个具体类型,并且你希望每次请求FooService
时都创建一个新的实例,你可以直接使用containerRegistry.Register<FooService>();
来注册。 -
如果
IBarService
是一个接口,而BarService
是实现这个接口的具体类,你希望每次请求IBarService
时都创建一个新的BarService
实例,你可以使用containerRegistry.Register<IBarService, BarService>();
来注册。
这两种方式都是在告诉Prism的依赖注入容器,当应用中需要FooService
或IBarService
时,应该每次都创建一个新的实例,而不是重用已有的实例。这种方式适用于那些不需要在应用的生命周期内保持状态的服务。
3.注册单例(Singleton)服务
-
单例服务的定义:在应用程序中,有些服务会被多次使用。如果每次都创建一个新的实例,这将不利于内存管理。因此,更好的做法是将这些服务注册为单例,这样它们就可以在整个应用程序中被重复使用。
-
单例服务的内存管理:单例服务在注册时并不会立即创建实例,它们不会开始占用内存,直到应用程序第一次解析(请求)这些服务时才会创建实例。
-
注册单例服务的代码示例:
// Where it will be appropriate to use FooService as a concrete type
containerRegistry.RegisterSingleton<FooService>();
containerRegistry.RegisterSingleton<IBarService, BarService>();
containerRegistry.RegisterSingleton<FooService>();
:这行代码将FooService
注册为一个单例服务。这意味着在整个应用程序中,FooService
只会有一个实例。containerRegistry.RegisterSingleton<IBarService, BarService>();
:这行代码将接口IBarService
和它的实现BarService
注册为一个单例服务。这样,任何时候请求IBarService
,都会返回同一个BarService
实例。
在Prism框架中,对于那些需要在整个应用程序中保持状态或频繁使用的服务,注册为单例是一种有效的做法,它有助于优化内存使用和提高应用程序性能。
4.注册服务实例
-
注册服务实例:通常情况下,我们会通过提供服务类型和服务实现类型来注册单例。但是,有时候我们可能需要直接创建一个服务实例,并将其注册为特定的服务。例如,如果我们有一个接口
IFoo
和一个实现FooImplementation
,我们可以直接创建FooImplementation
的实例,并将其注册为IFoo
的服务实例。containerRegistry.RegisterInstance<IFoo>(new FooImplementation());
-
使用插件中的当前实例:有时候,我们可能想要注册来自某个插件的当前实例。以James Montemagno的MonkeyCache插件为例,我们首先设置
Barrel.ApplicationId
为一个唯一的名称,然后注册Barrel.Current
作为IBarrel
的服务实例。Barrel.ApplicationId = "your_unique_name_here"; containerRegistry.RegisterInstance<IBarrel>(Barrel.Current);
在Prism框架中,除了通过服务类型和服务实现类型注册服务外,还可以直接注册服务实例,这在集成第三方插件或需要直接控制服务实例时非常有用。
5.检查一个服务是否已经被注册
if (containerRegistry.IsRegistered<ISomeService>())
{
// Do something...
}
-
检查服务是否注册:
- 在编写Prism模块或插件时,开发者可能需要检查某个服务是否已经被注册到依赖注入容器中。这可以通过
containerRegistry.IsRegistered<ISomeService>()
方法来实现,其中ISomeService
是你想要检查的服务接口。
- 在编写Prism模块或插件时,开发者可能需要检查某个服务是否已经被注册到依赖注入容器中。这可以通过
-
基于服务注册状态执行操作:
- 如果服务已经被注册,你可以执行一些特定的操作。例如,如果
IsRegistered<ISomeService>()
返回true
,你可以在大括号{}
中编写相应的代码来处理这种情况。
- 如果服务已经被注册,你可以执行一些特定的操作。例如,如果
-
注意事项:
- 如果你在Prism模块中有一个对特定服务的硬依赖,你应该通过构造函数注入该服务。这样做的好处是,如果在模块初始化时缺少该服务类型,将会抛出异常。这有助于在开发过程中及早发现问题。
- 只有当你的意图是注册一个默认实现时,才应该使用
IsRegistered
来检查服务是否已经注册。这意味着,如果服务尚未注册,你可以使用IsRegistered
检查后,注册一个默认的服务实现。
6.延迟解析(Lazy Resolution)服务
-
延迟解析服务:在Prism框架中,你可以像这样注册服务:
containerRegistry.Register<IFoo, Foo>()
。有些开发者可能希望节省内存,延迟加载服务,可以将其作为Func<IFoo>
或Lazy<IFoo>
来实现。Prism 8原生支持这种功能。要实现这一点,你只需要在你的ViewModel或Service中添加相应的参数,如下所示:public class ViewAViewModel { public ViewAViewModel(Func<IFoo> fooFactory, Lazy<IBar> lazyBar) { } }
-
注意服务注册类型:当你使用单例服务时,通常不建议使用
Lazy<T>
或Func<T>
进行解析。例如,IEventAggregator
是一个单例服务,这意味着整个应用程序中只使用一个事件聚合器实例。如果你使用Lazy<T>
或Func<T>
,最终可能会使用更多的内存,并且可能会因为延迟解析而影响性能,而不是直接请求服务。
这段内容强调了在Prism框架中实现延迟加载服务的方法,以及在使用单例服务时应避免使用Lazy<T>
或Func<T>
,以节省内存和提高性能。
7.全部解析(Resolve All)
-
Resolve All(全部解析):有些开发者可能需要注册同一个服务契约的多个实现,并期望在需要时能够解析出所有这些实现。这是一种常见的用例,例如Shiny框架在其一些委托接口中使用了这种模式。
-
构建模块化代码:通过这种方式,开发者可以构建更模块化的代码,因为可以对同一个事件做出响应,但是每次只处理一小部分(bite sized chunks)。
-
注册过程:对于这种特性,注册过程中没有什么特别的步骤。开发者只需要在构造函数中注入
IEnumerable<T>
接口即可。例如,如果你有一个IFoo
服务接口,并且有多个实现,你可以这样注入:public class SomeService { public SomeService(IEnumerable<IFoo> fooCollection) { // 这里的fooCollection将包含所有IFoo的实现 } }
-
支持情况:目前,只有DryIoc依赖注入容器支持这个特性。对于使用Unity容器的开发者来说,一旦Unity容器发布6.0版本,这个特性可能会变得可用。
相关链接
- 介绍(Introduction)
- 命令(Commands)
- 命令(Commanding)
- 复合命令(Composite Commands)
- 异步命令(Async Commands)
- 错误处理(Error Handling)
- 依赖注入(Dependency Injection)
- 依赖注入(Dependency Injection)
- 注册类型(Registering Types)