【WPF】Prism学习(七)

Prism Dependency Injection

1.注册类型(Registering Types)

1.1. Prism中的服务生命周期

  • Transient(瞬态):每次请求服务或类型时,都会获得一个新的实例。
  • Singleton(单例):每次请求服务或类型时,都会获得同一个实例。
  • Scoped(作用域):在每个容器作用域中获得一个新的实例,但在特定的容器作用域内保持同一个实例。

1.2. Prism中的默认作用域

  • Prism默认不使用作用域,除了在Prism.Maui中,它会为每个页面创建一个作用域。这种作用域用于INavigationServiceIPageDialogServiceIDialogService等服务。

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)可能是合适的。

csharp 复制代码
// Where it will be appropriate to use FooService as a concrete type
containerRegistry.Register<FooService>();

containerRegistry.Register<IBarService, BarService>();

具体来说,代码示例展示了两种注册瞬态服务的方式:

  1. 如果FooService是一个具体类型,并且你希望每次请求FooService时都创建一个新的实例,你可以直接使用containerRegistry.Register<FooService>();来注册。

  2. 如果IBarService是一个接口,而BarService是实现这个接口的具体类,你希望每次请求IBarService时都创建一个新的BarService实例,你可以使用containerRegistry.Register<IBarService, BarService>();来注册。

这两种方式都是在告诉Prism的依赖注入容器,当应用中需要FooServiceIBarService时,应该每次都创建一个新的实例,而不是重用已有的实例。这种方式适用于那些不需要在应用的生命周期内保持状态的服务。

3.注册单例(Singleton)服务

  1. 单例服务的定义:在应用程序中,有些服务会被多次使用。如果每次都创建一个新的实例,这将不利于内存管理。因此,更好的做法是将这些服务注册为单例,这样它们就可以在整个应用程序中被重复使用。

  2. 单例服务的内存管理:单例服务在注册时并不会立即创建实例,它们不会开始占用内存,直到应用程序第一次解析(请求)这些服务时才会创建实例。

  3. 注册单例服务的代码示例

csharp 复制代码
// 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.注册服务实例

  1. 注册服务实例 :通常情况下,我们会通过提供服务类型和服务实现类型来注册单例。但是,有时候我们可能需要直接创建一个服务实例,并将其注册为特定的服务。例如,如果我们有一个接口IFoo和一个实现FooImplementation,我们可以直接创建FooImplementation的实例,并将其注册为IFoo的服务实例。

    csharp 复制代码
    containerRegistry.RegisterInstance<IFoo>(new FooImplementation());
  2. 使用插件中的当前实例 :有时候,我们可能想要注册来自某个插件的当前实例。以James Montemagno的MonkeyCache插件为例,我们首先设置Barrel.ApplicationId为一个唯一的名称,然后注册Barrel.Current作为IBarrel的服务实例。

    csharp 复制代码
    Barrel.ApplicationId = "your_unique_name_here";
    containerRegistry.RegisterInstance<IBarrel>(Barrel.Current);

在Prism框架中,除了通过服务类型和服务实现类型注册服务外,还可以直接注册服务实例,这在集成第三方插件或需要直接控制服务实例时非常有用。

5.检查一个服务是否已经被注册

csharp 复制代码
if (containerRegistry.IsRegistered<ISomeService>())
{
    // Do something...
}
  1. 检查服务是否注册

    • 在编写Prism模块或插件时,开发者可能需要检查某个服务是否已经被注册到依赖注入容器中。这可以通过containerRegistry.IsRegistered<ISomeService>()方法来实现,其中ISomeService是你想要检查的服务接口。
  2. 基于服务注册状态执行操作

    • 如果服务已经被注册,你可以执行一些特定的操作。例如,如果IsRegistered<ISomeService>()返回true,你可以在大括号{}中编写相应的代码来处理这种情况。
  3. 注意事项

    • 如果你在Prism模块中有一个对特定服务的硬依赖,你应该通过构造函数注入该服务。这样做的好处是,如果在模块初始化时缺少该服务类型,将会抛出异常。这有助于在开发过程中及早发现问题。
    • 只有当你的意图是注册一个默认实现时,才应该使用IsRegistered来检查服务是否已经注册。这意味着,如果服务尚未注册,你可以使用IsRegistered检查后,注册一个默认的服务实现。

6.延迟解析(Lazy Resolution)服务

  1. 延迟解析服务 :在Prism框架中,你可以像这样注册服务:containerRegistry.Register<IFoo, Foo>()。有些开发者可能希望节省内存,延迟加载服务,可以将其作为Func<IFoo>Lazy<IFoo>来实现。Prism 8原生支持这种功能。要实现这一点,你只需要在你的ViewModel或Service中添加相应的参数,如下所示:

    csharp 复制代码
    public class ViewAViewModel
    {
        public ViewAViewModel(Func<IFoo> fooFactory, Lazy<IBar> lazyBar)
        {
        }
    }
  2. 注意服务注册类型 :当你使用单例服务时,通常不建议使用Lazy<T>Func<T>进行解析。例如,IEventAggregator是一个单例服务,这意味着整个应用程序中只使用一个事件聚合器实例。如果你使用Lazy<T>Func<T>,最终可能会使用更多的内存,并且可能会因为延迟解析而影响性能,而不是直接请求服务。

这段内容强调了在Prism框架中实现延迟加载服务的方法,以及在使用单例服务时应避免使用Lazy<T>Func<T>,以节省内存和提高性能。

7.全部解析(Resolve All)

  1. Resolve All(全部解析):有些开发者可能需要注册同一个服务契约的多个实现,并期望在需要时能够解析出所有这些实现。这是一种常见的用例,例如Shiny框架在其一些委托接口中使用了这种模式。

  2. 构建模块化代码:通过这种方式,开发者可以构建更模块化的代码,因为可以对同一个事件做出响应,但是每次只处理一小部分(bite sized chunks)。

  3. 注册过程 :对于这种特性,注册过程中没有什么特别的步骤。开发者只需要在构造函数中注入IEnumerable<T>接口即可。例如,如果你有一个IFoo服务接口,并且有多个实现,你可以这样注入:

    csharp 复制代码
    public class SomeService
    {
        public SomeService(IEnumerable<IFoo> fooCollection)
        {
            // 这里的fooCollection将包含所有IFoo的实现
        }
    }
  4. 支持情况:目前,只有DryIoc依赖注入容器支持这个特性。对于使用Unity容器的开发者来说,一旦Unity容器发布6.0版本,这个特性可能会变得可用。

相关链接

相关推荐
Chef_Chen2 小时前
从0开始学习R语言--Day18--分类变量关联性检验
学习
键盘敲没电2 小时前
【IOS】GCD学习
学习·ios·objective-c·xcode
Java Fans3 小时前
在WPF项目中集成Python:Python.NET深度实战指南
python·.net·wpf
海的诗篇_3 小时前
前端开发面试题总结-JavaScript篇(一)
开发语言·前端·javascript·学习·面试
AgilityBaby3 小时前
UE5 2D角色PaperZD插件动画状态机学习笔记
笔记·学习·ue5
AgilityBaby3 小时前
UE5 创建2D角色帧动画学习笔记
笔记·学习·ue5
武昌库里写JAVA4 小时前
iview Switch Tabs TabPane 使用提示Maximum call stack size exceeded堆栈溢出
java·开发语言·spring boot·学习·课程设计
一弓虽5 小时前
git 学习
git·学习
Moonnnn.7 小时前
【单片机期末】串行口循环缓冲区发送
笔记·单片机·嵌入式硬件·学习
viperrrrrrrrrr78 小时前
大数据学习(131)-Hive数据分析函数总结
大数据·hive·学习