我有一组从***Converter
继承的IConverter
个对象,目的是从字符串或XML反序列化对象:
public interface IConverter
{
object Convert(object value, Type targetType);
}
(我不知道编译时的类型,所以我不能在这里使用泛型。)
我使用ConvertsAttribute
标记转换器可以转换的类型,然后将其加倍为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);
}
}
在序列化模块中绑定转换器时,我在“转换”下附加了类型元数据:
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实例并在运行时查询它,我想避免它(它闻起来我能够查询我的converterKernel中的非IConverter对象)。 / p>
我可以在构造函数中简单地请求IEnumerable<IConverter>
,然后在每个转换器的类型上查询ConvertsAttribute,但我想知道是否有办法利用Ninject的元数据为我做这个...我可以请求我的构造函数中的IEnumerable<IConverter>
附带了元数据?在这个例子中,我想构建一个类型为IDictionary<Type, IConverter>
的字典,这意味着我不必查询容器本身。
答案 0 :(得分:1)
我不知道编译时的类型,所以我不能在这里使用泛型。
在编译时不知道类型,并不意味着你不能使用泛型。相反,我认为你应该使用泛型,因为:
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
。您可以将此接口的实现定义为composition root的一部分。当您将其置于此引导程序逻辑中时,您可以依赖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<>))