【问题标题】:StructureMap Generic Ctor Named InstanceStructureMap Generic Ctor 命名实例
【发布时间】:2017-12-04 22:47:59
【问题描述】:

更新

我用以下代码解决了这个问题,但这不是我正在寻找的解决方案。 对于更通用的解决方案,这仍然是一个开放的赏金。如果我们有一个不是 intstring 键值的表,我们将不得不手动添加让它发挥作用。

c.For(typeof(ILogDifferencesCommand<,>)).Use(typeof(LogDifferencesCommand<,>))
                .Ctor<ILogDifferencesLogger<int>>()
                .Named(AppSettingsManager.Get("logDifferences:Target"))
                .Ctor<string>()
                .Named(AppSettingsManager.Get("logDifferences:Target"));

原始问题

我有三种类型的记录器,我在容器中为它们定义了命名实例:

c.For(typeof(ILogDifferencesLogger<>))
    .Use(typeof(LogDifferencesAllLogger<>))
    .Named("all");
c.For(typeof(ILogDifferencesLogger<>))
    .Use(typeof(LogDifferencesNLogLogger<>))
    .Named("nlog");
c.For(typeof(ILogDifferencesLogger<>))
    .Use(typeof(LogDifferencesDatabaseLogger<>))
    .Named("database");

LogDifferencesCommand 接收 ILogDifferencesLogger&lt;&gt; 作为其唯一参数:

public LogDifferencesCommand(ILogDifferencesLogger<TKey> logDifferencesLogger)
{
    this.logDifferencesLogger = logDifferencesLogger;
}

如何正确配置ILogDifferencesCommand&lt;&gt; 以根据应用程序设置获取正确的命名实例?现在我有这样的东西:

c.For(typeof(ILogDifferencesCommand<,>))
    .Use(typeof(LogDifferencesCommand<,>));

我遇到的问题是我无法引入Ctor&lt;&gt;,因为我不能使用带有该签名的未绑定泛型,所以我不能使用Ctor 之外的Named 方法那个。

例如,我可以做这样的事情,但这不会影响所有可能的类型:

c.For(typeof(ILogDifferencesCommand<,>)).Use(typeof(LogDifferencesCommand<,>))
    .Ctor<ILogDifferencesLogger<int>>()
    .Named(AppSettingsManager.Get("logDifferences:Target"));

但问题是我必须处理系统使用的每个TKey 类型。

类和接口定义

public class LogDifferencesCommand<TModel, TKey> : ILogDifferencesCommand<TModel, TKey>
    where TModel : class, IIdModel<TKey>
{
    public LogDifferencesCommand(ILogDifferencesLogger<TKey> logDifferencesLogger)
    {
        this.logDifferencesLogger = logDifferencesLogger;
    }
}

public interface ILogDifferencesCommand<TModel, TKey>
    where TModel : class, IIdModel<TKey>
{
    List<LogDifference> CalculateDifferences(TModel x, TModel y);

    void LogDifferences(TModel x, TModel y, string tableName, string keyField, string userId, int? clientId);

    void RegisterCustomDisplayNameObserver(WeakReference<ICustomDisplayNameObserver<TModel>> observer);

    void RegisterCustomChangeDateObserver(WeakReference<ICustomChangeDateObserver<TModel>> observer);
}

public interface ILogDifferencesLogger<TKey>
{
    void LogDifferences(string tableName, string keyField, string userId, TKey id, List<LogDifference> differences, int? clientId);
}

之所以需要TKey,是因为IIdModel接口。

【问题讨论】:

  • 嗯,我觉得你有点想多了。只需创建 ILoggerFactory 并通过一些“创建”方法实例化您的 ILogDifferencesCommand。说真的,为了简单起见——它比 raping DI 框架更具可读性。无论哪种方式,您都使用 conventions 来实现这种反射。
  • @eocron 我知道你来自哪里。但是,这允许我们使用 DI 来获取实例,而不必在众多构造函数中添加调用。它使我们的课程非常简洁和一致。

标签: c# .net generics dependency-injection structuremap


【解决方案1】:

想到的一个选项:

var loggers = new Dictionary<string, ConfiguredInstance>();
loggers.Add("all", c.For(typeof(ILogDifferencesLogger<>))
    .Use(typeof(LogDifferencesAllLogger<>)));
loggers.Add("nlog", c.For(typeof(ILogDifferencesLogger<>))
    .Use(typeof(LogDifferencesNLogLogger<>)));
loggers.Add("database", c.For(typeof(ILogDifferencesLogger<>))
    .Use(typeof(LogDifferencesDatabaseLogger<>)));
foreach (var kv in loggers) {
    // if you still need them named
    // if you only used names for this concrete scenario - you probably don't
    // so can remove it
    kv.Value.Named(kv.Key);
}
c.For(typeof(LogDifferencesCommand<>))
    .Use(typeof(LogDifferencesCommand<>))
    // add explicit instance as dependency
    .Dependencies.Add(typeof(ILogDifferencesLogger<>), loggers[AppSettingsManager.Get("logDifferences:Target")]); 

更新。正如我们在 cmets 中发现的那样,当 LogDifferencesCommand 具有多个类型参数时,这不适用于您的特定情况。出于某种原因(我认为这是一个错误) - 结构映射尝试创建封闭的泛型类型ILogDifferencesLogger&lt;&gt;,但是这样做时 - 从LogDifferencesCommand 传递泛型类型参数。也许值得在他们的 github 上提出一个问题。您可以像这样解决它:

public class GenericTypesWorkaroundInstance : Instance
{
    private readonly Instance _target;
    private readonly Func<Type[], Type[]> _chooseTypes;
    public GenericTypesWorkaroundInstance(Instance target, Func<Type[], Type[]> chooseTypes) {
        _target = target;
        _chooseTypes = chooseTypes;
        ReturnedType = _target.ReturnedType;
    }

    public override Instance CloseType(Type[] types) {
        // close type correctly by ignoring wrong type arguments
        return _target.CloseType(_chooseTypes(types));
    }

    public override IDependencySource ToDependencySource(Type pluginType) {
        throw new NotSupportedException();
    }

    public override string Description => "Correctly close types over open generic instance";    
    public override Type ReturnedType { get; }
}

然后做

commandReg.Dependencies.Add(
    commandReg.Constructor.GetParameters().First(
        p => p.ParameterType.IsGenericType && p.ParameterType.GetGenericTypeDefinition() == typeof(ILogDifferencesLogger<>)).Name, 
        new GenericTypesWorkaroundInstance(
            loggers[AppSettingsManager.Get("logDifferences:Target")],
            // specify which types are correct
            types => types.Skip(1).ToArray()));

它有效,但我不能说我喜欢它。

【讨论】:

  • 这是一个非常优雅的解决方案,正是我正在寻找的,但现在它抛出了这个错误The number of generic arguments provided doesn't equal the arity of the generic type definition。我现在正在检查它,看看我可能做错了什么。
  • 也许您可以发布更完整的类定义,因为我在发布之前确实测试过(根据您的描述使用类和接口)。
  • 是的,我想我把你搞砸了 - 我的坏老板 - 问题是 ILogDifferencesCommand 需要两种类型 - TModelTKey - 所以我不得不稍微更改代码来构建到这个For(typeof(ILogDifferencesCommand&lt;,&gt;))。这就是构造函数为ILogDifferencesLogger 获取TKey 的地方。
  • 我用这个组合中的一些类型定义更新了我的原始问题 - 我很抱歉 - 我完全应该包括那些最初的。
  • 好吧,我本可以注意到ILogDifferencesCommand&lt;,&gt; 中的“,”。很奇怪,它在这种情况下不起作用,并且在 ILogDifferencesCommand 只有一个类型参数时起作用。无法理解它如何影响构造函数依赖关系,这两种情况都是相同的(但它确实如此)。
【解决方案2】:

StructureMap 的作者(我)强烈建议您尝试在应用程序引导时预先使用条件注册,通过检查配置值来选择默认记录器注册,然后只允许自动连接在运行时处理依赖关系。

【讨论】:

  • 您能提供一个解决方案示例吗?它不必是完整的,但我不能 100% 确定我理解该解决方案,并且该配置的一个小示例会很有帮助。
  • 哦,我想我知道你指的是什么——你指的是条件 if 语句吗?如果是这样,那将不起作用,因为这是一个不基于构建类型的配置值。
猜你喜欢
  • 1970-01-01
  • 2013-05-23
  • 1970-01-01
  • 2011-09-07
  • 2011-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多