简体   繁体   English

创建泛型类的通用工厂

[英]Generic factory that creates generic classes

I have a set of value type converters that convert strings into their respective types. 我有一组值类型转换器,可以将字符串转换为各自的类型。 I have a factory that is responsible for creating these converters based on the type, when needed. 我有一个工厂负责在需要时根据类型创建这些转换器。 I'm trying to keep the factory and converters generic, but I'm running into some issues. 我试图让工厂和转换器保持通用,但我遇到了一些问题。 I don't know the type until I call the .Create method on the factory, so I need to be able to pass in the type as an argument. 在工厂调用.Create方法之前我不知道类型,所以我需要能够将类型作为参数传递。 The trouble is that, then, my .Create method thinks I'm looking for a ValueConverter<Type> instead of a more appropriate value converter like ValueConverter<int> . 麻烦的是,然后,我的.Create方法认为我正在寻找ValueConverter<Type>而不是像ValueConverter<int>那样更合适的值转换器。 I'm missing something, or perhaps even doing it completely wrong. 我错过了什么,或者甚至做错了。

Here are a couple of my converters and the interface: 这是我的几个转换器和界面:

public interface IValueConverter<T>
{
    T Convert(object objectToConvert);
}

public class IntValueConverter : IValueConverter<int>
{
    public int Convert(object objectToConvert)
    {
        return System.Convert.ToInt32(objectToConvert);
    }
}

public class DateTimeValueConverter : IValueConverter<DateTime>
{
    public DateTime Convert(object objectToConvert)
    {
        return System.Convert.ToDateTime(objectToConvert);
    }
}

Then, I have a factory like this: 然后,我有一个这样的工厂:

public class ValueConverterFactory : IValueConverterFactory
{
    private readonly IUnityContainer _container;

    public ValueConverterFactory(IUnityContainer container)
    {
        _container = container;
    }

    public IValueConverter<T> Create<T>(T type)
    {
        return _container.Resolve<IValueConverter<T>>();
    }
}

And unity is configured something like this: 统一配置如下:

Container.RegisterType<IValueConverter<int>, IntValueConverter>();
Container.RegisterType<IValueConverter<DateTime>, DateTimeValueConverter>();

I need to be able to call the factory like this: 我需要能够像这样打电话给工厂:

var objectType = someObj.GetType();
var valueConverter = _valueConverterFactory.Create(objectType);

The trouble is that, then, my .Create method thinks I'm looking for a ValueConverter<Type> instead of a more appropriate value converter like ValueConverter<int> . 问题是,然后,我的.Create方法认为我正在寻找ValueConverter<Type>而不是像ValueConverter<int>那样更合适的值转换器。

First, you should understand why this is happening. 首先,你应该明白为什么会这样。 You didn't give us the invoking code, but it probably looks something like this: 你没有给我们调用代码,但它可能看起来像这样:

Type type = SomehowResolveTheTypeThatINeedToConvertTo();
factory.Create(type);

Right there, that is going to invoke the generic method 就在那里,那将调用泛型方法

IValueConverter<T> ValueConverterFactory.Create<T>(T type)

where Type is substituted for the type parameter T . 其中Type替换类型参数T

Second, you need to understand that what you are trying to fundamentally can't be done. 其次,你需要明白你所要做的事情是根本无法做到的。 You don't know the type at compile time, and therefore you can't have strong typing. 您在编译时不知道类型,因此您无法进行强类型输入。 To get back a strongly-typed IValueConverter<T> you need to know what T is. 要获得强类型的IValueConverter<T>您需要知道T是什么。 You either need to be willing to accept that your converters return object instead of T or find a way to have it be the case that you know the type T at compile-time. 您要么愿意接受转换器返回object而不是T要么找到一种方法让您在编译时知道类型T

I don't know if it's what your asking for but I found this elegant and short code in Rhino Igloo framework: ConversionUtil.cs - it converts string to any type... 我不知道这是不是你的要求,但我在Rhino Igloo框架中找到了这个优雅而简短的代码:ConversionUtil.cs - 它将字符串转换为任何类型......

    public static object ConvertTo(Type type, string inject)
    {
        if (inject == null)
        {
            return type.IsValueType ? Activator.CreateInstance(type) : null;
        }

        if (type.IsInstanceOfType(inject))
        {
            return inject;
        }
        else if (type == typeof(int))
        {
            int temp;
            if (int.TryParse(inject, out temp))
                return temp;
            return null;
        }
        else if (typeof(IConvertible).IsAssignableFrom(type))
        {
            return Convert.ChangeType(inject, type);
        }

        //Maybe we have a constructor that accept the type?
        ConstructorInfo ctor = type.GetConstructor(new Type[] { inject.GetType() });
        if (ctor != null)
        {
            return Activator.CreateInstance(type, inject);
        }

        //Maybe we have a Parse method ??
        MethodInfo parseMethod = type.GetMethod("Parse", BindingFlags.Static | BindingFlags.Public);
        if (parseMethod != null)
        {
            return parseMethod.Invoke(null, new object[] { inject });
        }

        throw new ArgumentException(string.Format(
            "Cannot convert value '{0}' of type '{1}' to request type '{2}'", 
            inject,
            inject.GetType(), 
            type));
    }

Maybe something like: 也许是这样的:

public IValueConverter<T> Create<T>(T type)
{
    return _container.Resolve(typeof(IValueConverter<>).MakeGenericType(type.GetType()));
}

though I haven't tested it. 虽然我还没有测试过。

On a completely different note, if you are just calling Convert under the covers anyway, why not just use System.Convert.ChangeType(Object, Type) ? 在完全不同的说明中,如果你只是在封面下调用Convert,为什么不使用System.Convert.ChangeType(Object, Type)

If you need to add support for custom types, you can create your own type converters and register them through the TypeConverterAttribute . 如果需要添加对自定义类型的支持,可以创建自己的类型转换器并通过TypeConverterAttribute注册它们。

This is typically done just as driushkin eluded to. 这通常就像滴露出来一样。 You'll need to register your open generic type with your container. 您需要在容器中注册开放的通用类型。 With StructureMap, this would be something like this: 使用StructureMap,这将是这样的:

Scan(scanner =>
{
    scanner.TheCallingAssembly();     
    scanner.ConnectImplementationsToTypesClosing(typeof (IValueConverter<>));
});

In Autofac, it would be something like this: 在Autofac中,它会是这样的:

builder.RegisterGeneric(typeof(IValueConverter<>));

You would then build up your generic type to resolve: 然后,您将构建通用类型以解决:

Type openType = typeof(IValueConverter<>);
Type closedType = openType.MakeGenericType(type);
var instance = container.Resolve(closedType);

I don't think you want your factory method's parameter to be generic though. 我不认为您希望工厂方法的参数是通用的。 It just needs to be a plain type: 它只需要是一个简单的类型:

public IValueConverter<T> Create<T>(Type type)
{
    // ...
}

Rather than creating all of this though, why not just use Automapper? 而不是创建所有这些,为什么不使用Automapper? Here's the types of mappings that I generally create: 这是我通常创建的映射类型:

public class DateTimeToDateMapping : IAutoMapperInitializer
{
    public void Initialize(IConfiguration configuration)
    {
        configuration.CreateMap<DateTime, Date>().ConstructUsing(
            dateTime => new Date(dateTime.Year, dateTime.Month, dateTime.Day));
    }
}

Here's how this mapping could be used: 以下是如何使用此映射:

var date = _mappingEngine.Map<DateTime, Date>(DateTime.Today);

I don't know that I've used System.Convert much, but it doesn't seem to be as intention revealing and I'm assuming it's hard-wired to only know how to convert from certain things. 我不知道我已经使用了System.Convert,但它似乎没有意图揭示,我认为它只是知道如何转换某些东西很难。 If you use Automapper then you can also test your mappings easily. 如果您使用Automapper,那么您也可以轻松地测试您的映射。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM