I needed a mechanism for creating types dynamicaly from strings and bytes etc.
I tried really hard to use the .net's TypeConverter
system as it seemed to be the right choice. Unfortunately it turned out to be extremely hard to customize (either by adding attributes to classes via reflection (which is like hacking it) or writing weird type description providers and alike that were a total overkill for such a simple task).
As a result I invented a new system for dynamic type creation. The purpose was to be extendable and super easy to use. I think it satisfies both goals. I'd like you to take a look at it and I'm eager to read your reviews :)
The type creator framework is based on one inteface:
public interface ITypeCreator
{
Type Type { get; }
bool CanCreateFrom(Type type);
object From(object value, CultureInfo culture = null);
}
and a base class that is a default implemenation for it. There is also a delegate that I use for invoking user code.
public delegate object InvokeFunc(object obj, object[] parameters);
public abstract class TypeCreator<T> : ITypeCreator
{
private readonly IDictionary<Type, InvokeFunc> _fromFuncs = new Dictionary<Type, InvokeFunc>();
protected TypeCreator()
{
InitializeFromFuncs();
}
public virtual Type Type { get; } = typeof(T);
private void InitializeFromFuncs()
{
var fromMethod = typeof(TypeCreator<>).GetMethod(nameof(From));
var fromMethods = GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public).Where(m =>
m.Name == nameof(From) &&
m.ReturnType == typeof(T) &&
m.GetParameters().Select(p => p.ParameterType).Skip(1)
.SequenceEqual(fromMethod.GetParameters().Select(p => p.ParameterType).Skip(1))
).ToList();
if (!fromMethods.Any())
{
throw new Exception(
"No <From> methods found. " +
"Make sure there is at least one with the signature: " +
"<ReturnType> From(<SourceType>, <CultureInfo>)");
}
foreach (var m in fromMethods)
{
_fromFuncs.Add(m.GetParameters().First().ParameterType, m.Invoke);
}
}
public bool CanCreateFrom(Type type)
{
return _fromFuncs.ContainsKey(type);
}
public object From(object value, CultureInfo culture = null)
{
var fromFunc = (InvokeFunc)null;
return
_fromFuncs.TryGetValue(value.GetType(), out fromFunc)
? fromFunc(this, new[] { value, culture ?? CultureInfo.InvariantCulture })
: null;
}
}
When I need a new type creator I just derive it from the TypeCreator
and implement as many From
methods and for as many types as I need to support. The signature for each method must be
ReturnType From(SupportedType, CultureInfo)
This is one of the type crators:
public class Int32Creator : TypeCreator<Int32>
{
public virtual int From(string value, CultureInfo culture)
{
return int.Parse(value, culture);
}
}
To store, register and lookup for type creators I use the below service:
public class TypeCreatorService
{
private readonly IDictionary<Type, ITypeCreator> _typeCreators = new Dictionary<Type, ITypeCreator>();
private CultureInfo _culture = CultureInfo.InvariantCulture;
public CultureInfo Culture
{
get { return _culture; }
set
{
if (value == null) { throw new ArgumentNullException(nameof(Culture)); }
_culture = value;
}
}
public void Add<TTypeCreator>() where TTypeCreator : ITypeCreator
{
var typeCreator = (ITypeCreator)Activator.CreateInstance(typeof(TTypeCreator));
_typeCreators.Add(typeCreator.Type, typeCreator);
}
public bool CanCreate(Type createType, Type fromType)
{
if (createType == null) { throw new ArgumentNullException(nameof(createType)); }
if (fromType == null) { throw new ArgumentNullException(nameof(fromType)); }
var typeCreator = (ITypeCreator)null;
return _typeCreators.TryGetValue(createType, out typeCreator) && typeCreator.CanCreateFrom(fromType);
}
public object Create(Type createType, object value)
{
if (createType == null) { throw new ArgumentNullException(nameof(createType)); }
if (value == null)
{
return null;
}
var typeCreator = (ITypeCreator)null;
return _typeCreators.TryGetValue(createType, out typeCreator) ? typeCreator.From(value, Culture) : null;
}
}
Finally the usage is just to create a TypeCeatorService
and add/register the types that it should support:
var typeCreatorService = new TypeCreatorService();
typeCreatorService.Add<Int32Creator>();
typeCreatorService.Add<StringCreator>();
typeCreatorService.Add<EnumCreator<ConsoleColor>>();
var number = typeCreatorService.Create(typeof(int), "123");
var data = typeCreatorService.Create(typeof(string), 123);
var consoleColor = typeCreatorService.Create(typeof(ConsoleColor), "Black");
var cannot = typeCreatorService.CanCreate(typeof(bool), typeof(string));
This might look trivial but it's a part of a bigger parser where types can by dynamically specified by a user so the lookup is also dynamic and the user is allowed to extend the type creator collection with his own implementetion.
The example is just about an int
but I have implemented it for all the basic types and a few custom ones.
There is also a StringCreator
that takes care of serializing various types:
class StringCreator : TypeCreator<string>
{
public virtual string From(string value, CultureInfo culture)
{
return value;
}
public virtual string From(int value, CultureInfo culture)
{
return value.ToString(culture);
}
}
and one more example for enums:
public class EnumCreator<TEnum> : TypeCreator<TEnum>
{
public EnumCreator()
{
if (typeof(TEnum).BaseType != typeof(Enum)) { throw new InvalidOperationException("TEnum must by of Enum type."); }
}
public override Type Type { get; } = typeof(TEnum);
public virtual TEnum From(string value, CultureInfo culture)
{
return (TEnum)Enum.Parse(Type, value);
}
}
You might be asking why there are some null checks missing. The parent project protects the type converters from nulls. It is also not possible to use generics everywhere because most types are reflected and not specified at runtime.