C# 如何装饰依赖于运行时值进行创建的类

C# 如何装饰依赖于运行时值进行创建的类,c#,dependency-injection,decorator,simple-injector,C#,Dependency Injection,Decorator,Simple Injector,虽然我已经使用Ninject很长时间了,但我对使用Simple Injector还是很陌生,所以总体上我对DI很满意。吸引我使用SimpleInjector的一个原因是装饰器的易用性 在请求服务时解决依赖关系的所有正常情况下,我都能够成功地使用带有简单注入器的装饰器。然而,当必须使用运行时值构造服务时,我很难弄清楚是否有一种方法可以应用我的decorator 在Ninject中,我可以将一个构造函数参数传递给内核。我想不出用简单的注入器复制的方法 我在下面放了一些非常基本的代码来说明。在现实世界

虽然我已经使用Ninject很长时间了,但我对使用Simple Injector还是很陌生,所以总体上我对DI很满意。吸引我使用SimpleInjector的一个原因是装饰器的易用性

在请求服务时解决依赖关系的所有正常情况下,我都能够成功地使用带有简单注入器的装饰器。然而,当必须使用运行时值构造服务时,我很难弄清楚是否有一种方法可以应用我的decorator

在Ninject中,我可以将一个
构造函数参数
传递给
内核。我想不出用简单的注入器复制的方法

我在下面放了一些非常基本的代码来说明。在现实世界中,我想做的是将
IMyClassFactory
实例传递到我的应用程序中的其他类中。然后,这些其他类可以使用它们提供的
IRuntimeValue
来使用它创建
IMyClass
实例。他们从
IMyClassFactory
获得的
IMyClass
实例将由注册的装饰者自动装饰

我知道我可以在我的
IMyClassFactory
或我能想到的任何
Func
中手动应用我的装饰程序,但我希望它“正常工作”

我不停地试图抽象出
MyClass
构造,但我不知道如何使用
IRuntimeValue
构造函数参数来解析它并进行修饰

我是否忽视了一个显而易见的解决方案

using System;
using SimpleInjector;
using SimpleInjector.Extensions;

public class MyApp
{
    [STAThread]
    public static void Main()
    {
        var container = new Container();
        container.Register<IMyClassFactory, MyClassFactory>();
        container.RegisterDecorator(typeof (IMyClass), typeof (MyClassDecorator));

        container.Register<Func<IRuntimeValue, IMyClass>>(
                 () => r => container.GetInstance<IMyClassFactory>().Create(r));

        container.Register<IMyClass>(() =>  ?????));  // Don't know what to do

        container.GetInstance<IMyClass>(); // Expect to get decorated class
    }
}

public interface IRuntimeValue
{
}

public interface IMyClass
{
    IRuntimeValue RuntimeValue { get; }
}

public interface IMyClassFactory
{
    IMyClass Create(IRuntimeValue runtimeValue);
}

public class MyClassFactory : IMyClassFactory
{
    public IMyClass Create(IRuntimeValue runtimeValue)
    {
        return new MyClass(runtimeValue);
    }
}

public class MyClass : IMyClass
{
    private readonly IRuntimeValue _runtimeValue;

    public MyClass(IRuntimeValue runtimeValue)
    {
        _runtimeValue = runtimeValue;
    }

    public IRuntimeValue RuntimeValue
    {
        get
        {
            return _runtimeValue;
        }
    }
}

public class MyClassDecorator : IMyClass
{
    private readonly IMyClass _inner;

    public MyClassDecorator(IMyClass inner)
    {
        _inner = inner;
    }

    public IRuntimeValue RuntimeValue
    {
        get
        {
            return _inner.RuntimeValue;
        }
    }
}
我的问题是,我希望我的
iccustomerviewmodel
由容器装饰,而更新它则绕过了这一点。现在我知道如何绕过这个限制


所以我想我接下来的问题是:我的设计一开始是错的吗?我真的觉得应该将
iccustomer
传递到
CustomerViewModel
的构造函数中,因为这表明它是必需的、得到验证的,等等。我不想在事后添加它

Simple Injector明显不支持通过
GetInstance
方法传递运行时值。原因是在构建对象图时不应使用运行时值。换句话说,的构造函数不应该依赖于运行时值。这样做有几个问题。首先,注射剂可能需要比那些运行时值活得更长。但是,也许更重要的是,您希望能够在对象图中使用运行时值,并且能够更改容器的配置,这会变得更加麻烦

一般来说,有两种解决方案。要么通过方法调用图传递运行时值,要么创建一个“上下文”服务,在请求时提供此运行时值

当您实践通过系统传递消息的体系结构时,或者当运行时值可能是服务契约的一个明显部分时,通过调用图传递运行时值尤其是一个有效的解决方案。在这种情况下,很容易通过消息或方法传递运行时值,并且该运行时值也将在传递过程中通过任何装饰器

在您的情况下,这意味着工厂创建
IMyService
,而不传入
IRuntimeValue
,并且您的代码使用指定的方法将此值传递给
IMyService

var service = _myServiceFactory.Create();
service.DoYourThing(runtimeValue);
然而,通过调用图传递运行时值并不总是一个好的解决方案。尤其是当此运行时值不应成为所发送消息的协定的一部分时。这尤其适用于上下文信息,用作有关当前登录用户、当前系统时间等的信息。您不希望传递这些信息;你只是想让它可用。我们不希望这样,因为这会给消费者带来额外的负担,让他们每次都传递正确的值,而他们甚至可能无法更改此信息(以执行请求的用户为例)

在这种情况下,您应该定义可以注入并允许检索此上下文的服务。例如:

public interface IUserContext {
    User CurrentUser { get; }
}

public interface ITimeProvider {
    DateTime Now { get; }
}
在这些情况下,当前用户和当前时间不会直接注入构造函数,而是注入这些服务。需要访问当前用户的组件只需调用
\u userContext.CurrentUser
,这将在构建对象后完成(读取:而不是构造函数内部的)。因此:以一种懒惰的方式

但是,这意味着必须在调用
MyClass
之前的某个位置设置
IRuntimeValue
。这可能意味着您需要在工厂内进行设置。下面是一个例子:

var container = new Container();
var context = new RuntimeValueContext();
container.RegisterSingle<RuntimeValueContext>(context);
container.Register<IMyClassFactory, MyClassFactory>();
container.RegisterDecorator(typeof(IMyClass), typeof(MyClassDecorator));
container.Register<IMyClass, MyClass>();

public class RuntimeValueContext {
    private ThreadLocal<IRuntimeValue> _runtime;
    public IRuntimeValue RuntimeValue {
        get { return _runtime.Value; }
        set { _runtime.Value = value; }
    }
}

public class MyClassFactory : IMyClassFactory {
    private readonly Container _container;
    private readonly RuntimeValueContext context;
    public MyClassFactory(Container container, RuntimeValueContext context) {
        _container = container;
        _context = context;
    }

    public IMyClass Create(IRuntimeValue runtimeValue) {
        var instance = _container.GetInstance<IMyClass>();
        _context.RuntimeValue = runtimeValue;
        return instance;
    }
}

public class MyClass : IMyClass {
    private readonly RuntimeValueContext _context;
    public MyClass(RuntimeValueContext context) {
        _context = context;
    }
    public IRuntimeValue RuntimeValue { get { return _context.Value; } }
}
但是不允许验证对象图,因为Simple Injector将确保注册永远不会返回null,但是默认情况下,
context.Value
将为null。因此,另一个选择是执行以下操作:

container.Register<IMyClass>(() => new MyClass(context.Value));
container.Register(()=>newmyclass(context.Value));

这允许验证
IMyClass
注册,但在验证期间仍将创建一个新的
MyClass
实例,该实例将被注入空值。如果在
MyClass
构造函数中有一个guard子句,则此操作将失败。但是,此注册不允许容器自动连接
MyClass
。例如,当您有更多依赖项要注入到
MyClass
中时,自动连接该类会很方便。

此外,如果服务依赖于
container.Register<IRuntimeValue>(() => context.Value);
container.Register<IMyClass>(() => new MyClass(context.Value));