Reputation: 13002
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. 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>
. 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);
Upvotes: 4
Views: 1384
Reputation: 16262
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:
Scan(scanner =>
{
scanner.TheCallingAssembly();
scanner.ConnectImplementationsToTypesClosing(typeof (IValueConverter<>));
});
In Autofac, it would be something like this:
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? 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. If you use Automapper then you can also test your mappings easily.
Upvotes: 0
Reputation: 3659
Maybe something like:
public IValueConverter<T> Create<T>(T type)
{
return _container.Resolve(typeof(IValueConverter<>).MakeGenericType(type.GetType()));
}
though I haven't tested it.
Upvotes: 0
Reputation: 241611
The trouble is that, then, my
.Create
method thinks I'm looking for aValueConverter<Type>
instead of a more appropriate value converter likeValueConverter<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
.
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. 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.
Upvotes: 1
Reputation: 4736
On a completely different note, if you are just calling Convert under the covers anyway, why not just use 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
.
Upvotes: 0
Reputation: 10661
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...
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));
}
Upvotes: 0