C# 反应式扩展延迟初始化

C# 反应式扩展延迟初始化,c#,system.reactive,simple-injector,C#,System.reactive,Simple Injector,对于使用SimpleInjector解析的类型,在CTOR中进行工作是不好的做法,这一点已经相当确定。尽管这通常会导致此类类型的某些后期初始化,但一个特别有趣的情况是被动扩展订阅 例如,一个可观察的序列显示出Replay(1)语义(如果我们将StartWith考虑在内,实际上是BehaviorSubject),例如 private readonly IObservable\u myobbservable; 公共MyType(iSeries服务) { _myObservable=service.O

对于使用SimpleInjector解析的类型,在CTOR中进行工作是不好的做法,这一点已经相当确定。尽管这通常会导致此类类型的某些后期初始化,但一个特别有趣的情况是被动扩展订阅

例如,一个可观察的序列显示出
Replay(1)
语义(如果我们将
StartWith
考虑在内,实际上是
BehaviorSubject
),例如

private readonly IObservable\u myobbservable;
公共MyType(iSeries服务)
{
_myObservable=service.OtherObservable
.StartWith(service.Value)
.Select(x=>SomeTransform())
.重播(1)
.RefCount();
}
public IObservable myobbservable=>\u myobbservable;
现在假设
SomeTransform
在计算上很昂贵。从SimpleInjector的角度来看,上述做法是不好的。好的,我们需要在SimpleInjector完成后调用某种
Initialize()
方法。但是我们的重播语义和我们的
StartWith()
呢?我们的消费者在订阅时希望得到一个值(现在假设这保证在初始化后发生)

我们如何在满足SimpleInjector的同时以一种好的方式绕过这些限制?以下是需求摘要:

  • 不要在不应运行的ctor中执行大量工作(即
    SomeTransform
  • \u myObservable
    应为
    只读
  • MyObservable
    应显示
    Replay(1)
    语义
  • 我们应该始终有一个初始值(因此,
    StartWith
  • 我们不希望
    订阅
    内部
    MyType
    并缓存该值(我们喜欢不变性)

  • 我尝试创建一个附加的可观察对象,它从
    false
    开始,然后在初始化时设置为
    true
    ,然后将其与
    \u myObservable
    合并,但无法完全实现。此外,这似乎不是最好的解决方案。本质上,我所要做的就是延迟到
    Initialize()
    完成。一定有什么方法可以做到这一点,但我看不到?

    想到的一个简单的解决方案是使用

    这可能看起来像:

    private readonly Lazy<IObservable<Value>> _lazyMyObservable;
    
    public MyType(IService service)
    {
        _lazyMyObservable =  new Lazy<IObservable<Value>>(() => this.InitObservable(service));
    }
    
    private IObservable<Value> InitObservable(IService service)
    {
        return service.OtherObservable
            .StartWith(service.Value)
            .Select(x => SomeTransform())
            .Replay(1)
            .RefCount();
     }
    
     public IObservable<Value> MyObservable => _lazyMyObservable.Value;
    
    private readonly Lazy _lazyMyObservable;
    公共MyType(iSeries服务)
    {
    _lazyMyObservable=newlazy(()=>this.InitObservable(服务));
    }
    私有IObservable InitObservable(iSeries服务)
    {
    返回服务。其他可观察的
    .StartWith(service.Value)
    .Select(x=>SomeTransform())
    .重播(1)
    .RefCount();
    }
    public IObservable MyObservable=>\u lazyMyObservable.Value;
    
    这将初始化变量
    \u lazyMyObservable
    ,而不实际调用
    SomeTransform()
    。当消费者要求
    MyType.MyObservable
    时,
    InitObservable
    代码将被调用一次且仅调用一次。这会将初始化延迟到实际使用代码的位置

    这将使您的构造函数保持整洁,无需添加初始化逻辑


    请注意,
    Lazy
    的ctor有几个重载,如果您可能有多线程问题,可以使用这些重载

    想到的一个简单的解决方案是使用

    这可能看起来像:

    private readonly Lazy<IObservable<Value>> _lazyMyObservable;
    
    public MyType(IService service)
    {
        _lazyMyObservable =  new Lazy<IObservable<Value>>(() => this.InitObservable(service));
    }
    
    private IObservable<Value> InitObservable(IService service)
    {
        return service.OtherObservable
            .StartWith(service.Value)
            .Select(x => SomeTransform())
            .Replay(1)
            .RefCount();
     }
    
     public IObservable<Value> MyObservable => _lazyMyObservable.Value;
    
    private readonly Lazy _lazyMyObservable;
    公共MyType(iSeries服务)
    {
    _lazyMyObservable=newlazy(()=>this.InitObservable(服务));
    }
    私有IObservable InitObservable(iSeries服务)
    {
    返回服务。其他可观察的
    .StartWith(service.Value)
    .Select(x=>SomeTransform())
    .重播(1)
    .RefCount();
    }
    public IObservable MyObservable=>\u lazyMyObservable.Value;
    
    这将初始化变量
    \u lazyMyObservable
    ,而不实际调用
    SomeTransform()
    。当消费者要求
    MyType.MyObservable
    时,
    InitObservable
    代码将被调用一次且仅调用一次。这会将初始化延迟到实际使用代码的位置

    这将使您的构造函数保持整洁,无需添加初始化逻辑


    请注意,
    Lazy
    的ctor有几个重载,如果您可能有多线程问题,可以使用这些重载

    注入构造函数应该是和。这意味着不赞成以下做法:

    • 在构造函数内执行任何I/O操作。I/O操作可能会失败,并使对象图的构造不可靠
    • 在构造函数中使用类的依赖项。被调用的依赖项不仅会导致其自身的I/O,有时注入的依赖项还没有(尚未)完全初始化,最终初始化会在稍后的时间点发生。也许在构建对象图之后
    考虑到反应式扩展的工作方式,您的
    MyType
    构造函数似乎没有执行任何I/O操作。在创建
    MyType
    期间,不会调用其
    SomeTransform
    方法。相反,可观察对象被配置为在推送对象时调用
    SomeTransform
    。这意味着从DI的角度来看,您的注入仍然是“简单”和快速的。有时,您的类需要在存储传入依赖项的基础上进行一些初始化。例如,创建和存储一个
    惰性的
    ,就是一个很好的例子。它允许延迟执行一些I/O,同时仍然拥有比“接收依赖项”更多的代码

    但您仍然在访问构造函数中的依赖项,如果该依赖项或其依赖项未完全初始化,则可能会导致问题。此外,使用反应式扩展,您可以从
    IService
    返回到
    MyType
    (您已经从获得了设计时依赖性)