2
\$\begingroup\$

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.

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

OK, second attempt (updated). Could you please tell me if something is missing in this scenario?

        // 4 ways to setup 
        DynamicActivator activator = CompositeActivator.Empty
            .Support<DemoActivator>()
            .Support(new DemoActivator())
            .Support((int arg) => arg.ToString()) 
            .Support((double arg) => arg.ToString()) 
            + new DemoActivator();

        // no need to know arg type
        string text1 = activator.Create<string>(42);
        string text2 = (string)activator.Create(typeof(string), 42);

I defined this demo activator as (see below for the base class):

class DemoActivator : ConversionActivator<int, string>
{
    protected override string CreateCore(int arg, CultureInfo culture) =>
        arg.ToString();
}

Does this immutable configuration work? Do I have a right API now?

Library code – abstraction to be consumed (with a lot of helpers):

public abstract class DynamicActivator
{
    // the only method to implement
    public abstract bool TryCreate(Type type, object arg, CultureInfo culture, out object instance);

    // helpers
    public T Create<T>(object arg) =>
        Create<T>(arg, CultureInfo.InvariantCulture);

    public T Create<T>(object arg, CultureInfo culture) =>
        (T)Create(typeof(T), arg, CultureInfo.InvariantCulture);

    public bool TryCreate<T>(object arg, out T instance) =>
        TryCreate<T>(arg, CultureInfo.InvariantCulture, out instance);

    public bool TryCreate<T>(object arg, CultureInfo culture, out T instance)
    {
        instance = default(T);
        object value = null;
        if (!TryCreate(typeof(T), arg, CultureInfo.InvariantCulture, out value))
            return false;

        instance = (T)value;
        return true;
    }

    public object Create(Type type, object arg) =>
        Create(type, arg, CultureInfo.InvariantCulture);

    public object Create(Type type, object arg, CultureInfo culture)
    {
        object instance;
        if (!TryCreate(type, arg, culture, out instance))
            throw new NotSupportedException();

        return instance;
    }

    public bool TryCreate(Type type, object arg, out object instance) =>
        TryCreate(type, arg, CultureInfo.InvariantCulture, out instance);        

    // composition
    public DynamicActivator Support<TActivator>()
        where TActivator : DynamicActivator, new() =>
        this + new TActivator();

    public DynamicActivator Support(DynamicActivator activator) =>
        this + activator;

    public DynamicActivator Support<TArg, TResult>(Converter<TArg, TResult> converter) =>
        this + new RelayActivator<TArg, TResult>(converter);

    public static DynamicActivator operator+(DynamicActivator left, DynamicActivator right) =>
        new CompositeActivator(left, right);        
}

And composite pattern:

public class CompositeActivator : DynamicActivator
{
    public static readonly DynamicActivator Empty = new CompositeActivator();

    public CompositeActivator(params DynamicActivator[] activators)
    {
        Activators = activators;
    }

    public override bool TryCreate(Type type, object arg, CultureInfo culture, out object instance)
    {
        instance = null;
        foreach (var activator in Activators)
            if (activator.TryCreate(type, arg, culture, out instance))
                return true;

        return false;
    }

    DynamicActivator[] Activators { get; }
}

And base class for custom activators:

public abstract class ConversionActivator<TArg, TResult> : DynamicActivator
{
    public override bool TryCreate(Type type, object arg, CultureInfo culture, out object instance)
    {
        instance = null;
        if (type.IsAssignableFrom(typeof(TResult)))
            if (arg is TArg)
            {
                instance = CreateCore((TArg)arg, culture);
                return true;
            }

        return false;
    }

    protected abstract TResult CreateCore(TArg arg, CultureInfo culture);
}

And a delegate adaptor:

public class RelayActivator<TArg, TResult> : ConversionActivator<TArg, TResult>
{
    public RelayActivator(Converter<TArg, TResult> converter)
    {
        Converter = converter;
    }

    protected override TResult CreateCore(TArg arg, CultureInfo culture) =>
        Converter(arg);

    Converter<TArg, TResult> Converter { get; }
}

Please note that the solution is type-safe (no reflection), thread-safe (immutable) and testable (DynamicActivator is an abstraction). The only static field is an immutable empty composite activator singleton, which should be OK.

\$\endgroup\$
5
  • \$\begingroup\$ I like it. Now it's my turn :-] I'll try to adapt it to my code with a small change. I'll use a dictionary in the composite activator for faster look up. \$\endgroup\$
    – t3chb0t
    Jun 12, 2016 at 6:58
  • \$\begingroup\$ You are always welcome! :) \$\endgroup\$ Jun 12, 2016 at 10:16
  • \$\begingroup\$ I've implemented most of your suggestions in here I did not borrow the RelayActivator as I couldn't figure out its purpose. I should have asked but I managed to build a new architecture without it :-) maybe you could still explain what is it for? I like it much better now without the reflection and forcing the user to override a concrete method which is safer then a signature that a method has to follow. \$\endgroup\$
    – t3chb0t
    Jun 12, 2016 at 12:19
  • \$\begingroup\$ I tried to use a dictionary but it would be an overkill too. I had to create a new hashcode for both types of each converter. I found it is too much work for this tiny performance gain. I think it's perfeclty fine to use an array. \$\endgroup\$
    – t3chb0t
    Jun 12, 2016 at 12:22
  • \$\begingroup\$ @t3chb0t RelayActivator is an adaptor - it allows to use delegates (lambdas) as an Activator. \$\endgroup\$ Jun 12, 2016 at 15:41
2
\$\begingroup\$

It does not sound like Type creation for me… We are not generating code at run time here.

What we deal here with is just a set of converters, which is solvable though the composite pattern. Let’s define an abstraction (you could extend it with CultureInfo):

public interface IConverter
{
    bool CanConvert<T, TResult>();
    TResult Convert<T, TResult>(T value);
} 

Now base implementation:

public abstract class Converter : IConverter
{
    public static readonly Converter Default = new NullConverter();

    public abstract bool CanConvert<T, TResult>();
    public abstract TResult Convert<T, TResult>(T value);

    public Converter Support<T, TResult>(Func<T, TResult> conversion) =>
        this + new RelayConverter<T, TResult>(conversion);

    public static Converter operator+(Converter left, Converter right) =>
        new CompositeConverter(left, right);
}

Where:

public class NullConverter : Converter
{
    public override bool CanConvert<T, TResult>() => false;
    public override TResult Convert<T, TResult>(T value)
    {
        throw new NotSupportedException();
    }
}

And:

public class RelayConverter<TC, TCResult> : Converter
{
    public RelayConverter(Func<TC, TCResult> conversion)
    {
        Conversion = conversion;
    }

    public override bool CanConvert<T, TResult>() =>
        typeof(TC).IsAssignableFrom(typeof(T)) &&
        typeof(TResult).IsAssignableFrom(typeof(TCResult));

    public override TResult Convert<T, TResult>(T value)
    {
        if (!CanConvert<T, TResult>())
            throw new NotSupportedException();

        // requires boxing/unboxing :(
        return (TResult)(object)Conversion((TC)(object)value);
    }

    Func<TC, TCResult> Conversion { get; }
}

And:

public class CompositeConverter : Converter
{
    public CompositeConverter(params Converter[] converters)
    {
        Converters = converters;
    }

    public override bool CanConvert<T, TResult>() =>
        Converters.Any(c => c.CanConvert<T, TResult>());

    public override TResult Convert<T, TResult>(T value) =>
        Converters
            .Where(c => c.CanConvert<T, TResult>())
            .DefaultIfEmpty(Default)
            .First()
            .Convert<T, TResult>(value);

    IEnumerable<Converter> Converters { get; }
}

Demo:

    static void Main(string[] args)
    {
        IConverter converter = Converter.Default
            .Support((string v) => int.Parse(v))
            .Support((int v) => v.ToString());

        Console.WriteLine(converter.Convert<int, string>(13));
        Console.WriteLine(converter.Convert<string, int>("13"));
    }
\$\endgroup\$
6
  • \$\begingroup\$ This is not bad but it's too simple ;-) For one I cannot use generics like this because as I mentioned the types come via reflection from classes that a user can freely define and I resolve them dynamicaly. The other issue is that serializing a XDocument requires to use memory streams etc. and defining all this as lambdas would be difficult to test and to override for the user. I deliberately chose to not name it converter because there are already so many of them. The last thing is that it shouldn't be static. I try to avoid statics as hell :] they are extremely difficult to test. \$\endgroup\$
    – t3chb0t
    Jun 11, 2016 at 21:30
  • \$\begingroup\$ I find it quite interesting how you chain the converters with the relay and composite converters. I probably should also explain why I decided to use the interface. The fact that the base class is a generic class doesn't allow it to be stored in a dictionary or any other strongly typed collection. Anyway +1 for the great review, thx ;-) \$\endgroup\$
    – t3chb0t
    Jun 11, 2016 at 21:50
  • \$\begingroup\$ @t3chb0t Thanks. I just can not get this "type creator" name :) Does not it look like an abstract factory for you? When real type is not known. The closest term in .NET is Activator. \$\endgroup\$ Jun 11, 2016 at 21:54
  • \$\begingroup\$ You're right. As a matter of fact they are factories. What would you say if I named it TypeFactory? I actually like it even better because I realy want to avoid the converter as there is already a whole bunch of them in .net . I wonder myself why I didn't think about a factory until now :-] \$\endgroup\$
    – t3chb0t
    Jun 11, 2016 at 22:01
  • \$\begingroup\$ TypeFactory suppose code emitting for me, I would not use Type - may be RelayActivator. It delegates; Activator is well known. Let me try to type something in, it is a good exercise :) \$\endgroup\$ Jun 11, 2016 at 22:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.