0

我有一组***Converter继承自的对象,IConverter目的是从字符串或 XML 反序列化对象:

public interface IConverter
{
    object Convert(object value, Type targetType);
}

(我不知道编译时的类型,所以我不能在这里使用泛型)。

我使用 aConvertsAttribute来标记转换器可以转换的类型,然后将其加倍为 Ninject ConstraintAttribute

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class ConvertsAttribute : ConstraintAttribute
{
    public Type TargetType { get; private set; }
    public ConvertsAttribute(Type t)
    {
        TargetType = t;
    }

    public override bool Matches(IBindingMetadata metadata)
    {
        return metadata.Has("Converts") && metadata.Get<Type>("Converts") == this.TargetType;
    }
}

[Converts(typeof(Int32))]
[Converts(typeof(Single))]
[Converts(typeof(String))]
[Converts(typeof(Double))]
public class BasicConverter : IConverter
{
    public object Convert(object value, Type targetType)
    {
        return System.Convert.ChangeType(value, targetType);
    }
}

在我的序列化模块中绑定转换器时,我在“Converts”下附加了类型元数据:

    private void BindConverter(Type typeInfo)
    {
        var converterAttributes = typeInfo.GetCustomAttributes(typeof(ConvertsAttribute), true);

        foreach (var attribute in converterAttributes.Cast<ConvertsAttribute>())
            Bind<IConverter>().To(typeInfo).WithMetadata("Converts", attribute.TargetType);
    }

解析类型时,我可以在容器中查询转换特定类型的转换器:

    private IConverter GetConverter(Type t)
    {
        return converterKernel.Get<IConverter>(metadata => t == metadata.Get<Type>("Converts"));
    }

但是,这意味着我必须在我的类构造函数中获取 IKernel 实例并在运行时查询它,这是我想避免的(闻起来我可以查询我的 convertKernel 以获取非 IConverter 对象)。

我可以简单地IEnumerable<IConverter>在构造函数中请求,然后在每个转换器的类型上查询 ConvertsAttribute,但我想知道是否有办法利用 Ninject 的元数据为我执行此操作......我可以IEnumerable<IConverter>在构造函数中请求附加元数据吗?在这种情况下,我想构建一个类型的字典,IDictionary<Type, IConverter>这意味着我不必查询容器本身。

4

1 回答 1

1

我不知道编译时的类型,所以我不能在这里使用泛型。

在编译时不知道类型,并不意味着您不能使用泛型。相反,我认为你应该使用泛型,因为:

  • 它允许您(轻松地)通过它们的通用接口批量注册这些类型,
  • 它允许您一次请求正确的转换器,而不必请求全部并遍历它们以找出要使用的转换器,并且不必用 a 装饰类型ConvertsAttribute(因为通过实现该信息已经在元数据中可用)通用接口),
  • System.Convert.ChangeType它允许您为缺少自定义转换器的任何类型定义默认(后备)实现(映射到)。

您仍然需要非泛型IConverter接口,并且没有编译时类型的使用者可以依赖该接口。但除了这个非泛型接口之外,您还可以定义以下泛型接口:

public interface IConverter<T>
{
    T Convert(object value);
}

您现在可以通过实现该接口来定义自定义转换器:

public class MyTypeConverter : IConverter<MyType>
{
    public MyType Convert(object value) { ... }
}

您可以定义一个可以用作“后备”实现的通用实现。当没有为请求的类型注册自定义转换器时,可以使用此实现:

public class DefaultConverter<T> : IConverter<T>
{
    public T Convert(object value)
    {
        return System.Convert.ChangeType(value, targetType);
    }
}

由于您的消费者在编译时没有任何可用的类型信息,因此让非泛型IConverter闲逛仍然很方便。您可以将此接口的实现定义为组合根的一部分。当您将其放置在此引导程序逻辑中时,您可以依赖Kernel,而无需将容器用作Service Locator

public class NinjectConverter : IConverter
{
    private readonly Kernel kernel;

    public NinjectConverter(Kernel kernel)
    {
        this.kernel = kernel;
    }

    public object Convert(object value, Type targetType)
    {
        var converterType =
            typeof(IConverter<>).MakeGenericType(targetType);

        dynamic converter = this.kernel.Get(converterType);

        return converter.Convert(value);
    }
}

此实现使用反射和动态类型。这将不是性能最好的代码,但与 Ninject 的开销相比,开销可能微不足道。

您现在可以轻松地批量注册所有定义的自定义转换器,如下所示:

var assembly = typeof(IConverter<>).Assembly;

var converterRegistrations =
    from type in assembly.GetExportedTypes()
    where !type.IsAbstract && !type.IsGenericTypeDefinition
    from service in type.GetInterfaces()
    where service.IsGenericType
    where service.GetGenericTypeDefinition() == 
        typeof(IConverter<>)
    select { service, type };

foreach (var registration in converterRegistrations)
{
    kernel.Bind(registration.service).To(registration.type);
}

我希望有一种更简单的方法来使用 Ninject(也许是单线)来做到这一点,但老实说,我对 Ninject 做得不够好。对于阅读本文的任何人:如果您知道更好的方法,请更新我的答案。

您可以像这样注册您的后备转换器:

kernel.Bind(typeof(IConverter<>))
    .To(typeof(DefaultConverter<>))
于 2012-10-21T20:10:52.157 回答