HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Type converter framework (v2)

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
typeframeworkconverter

Problem

This is the second version of my type converter framework.

The the previous one can be found here: Type creator service & framework

In this version I mostly implemented what @Dmitry Nogin suggested in his great reviews

I also extended it so that each converter has access to all the registered converters in case it needs them like for creating collections.

After giving it another thougt I changed my mind again it went back to name it just converters :-) neither activator, nor factory nor anything else seem to be ok.

The framework's goals are:

  • super easy to use - the user needs to override only one method



  • super easy to extend - the user can create any converter he wants and also use any registered converter



  • type safe



  • can be used without generics - types are resolved dynamicaly from properties of other classes at runtime



Base class for all converters:

public abstract class TypeConverter
{
    public CompositeConverter CompositeConverter { get; internal set; }

    public abstract bool TryConvert(Type type, object arg, CultureInfo culture, out object instance);
}


Composite converter to hold multiple converters:

```
public class CompositeConverter : TypeConverter
{
private CompositeConverter() { }

private CompositeConverter(CompositeConverter compositeConverter, TypeConverter typeConverter)
{
Converters =
compositeConverter.Converters
.Concat(new TypeConverter[] { typeConverter}).ToArray();

// update the composite converter
foreach (var activator in Converters)
{
typeConverter.CompositeConverter = this;
}
}

public TypeConverter[] Converters { get; private set; } =
Enumerable.Empty().ToArray();

public CompositeConverter Register() where TConverter : TypeConverter, new()
{
return (this + new TConverter());
}

public override bool TryConvert(Type type, object arg, CultureInfo culture, out object instance)
{

Solution

Thanks for sharing – it is an interesting problem :)

  • I would go with mutable state only when it is required for performance optimization. Immutability almost always makes code a way cleaner and maintainable.



  • We should not depend on implementation – let’s prefer abstraction where possible. I mean we should work with TypeConverter, not CompositeConverter to define a service.



Here is an example. Demo code:

class Program
{
    static void Main(string[] args)
    {
        TypeConverter converter = TypeConverter.Default
            .Register();

        Console.WriteLine(
            converter.Convert(
                typeof(int), "42", CultureInfo.InvariantCulture));
    }
}


Where:

public class StringToInt32Converter : TypeConverter
{
    protected override int Convert(string arg, ConversionContext context) =>
        int.Parse(arg, context.Culture);
}


Now library. Let’s define our abstraction as clean as possible:

public abstract class TypeConverter
    {
         public static readonly TypeConverter Default = CompositeConverter.Empty;
         protected internal abstract bool TryConvert(
             ConversionContext context, object arg, out object instance);
    }


And all the API with multiple overloads will come as two sets of extension methods – I really like this trick :)

public static class Composition
{
    public static TypeConverter Register(this TypeConverter that)
        where TConverter : TypeConverter, new() =>
        that.Register(new TConverter());

    // etc…

    // base method to be used above
    public static TypeConverter Register(this TypeConverter that, TypeConverter converter) =>
        new CompositeConverter(that, converter);
}


And:

public static class Conversions
{
    public static object Convert(this TypeConverter converter, Type type, object arg, CultureInfo culture)
    {
        object instance;
        if (!converter.TryConvert(type, arg, culture, out instance))
            throw new NotSupportedException();

        return instance;
    }

    // etc

    // base method to be used above
    public static bool TryConvert(this TypeConverter converter, Type type, object arg, CultureInfo culture, out object instance) =>
        converter.TryConvert(new ConversionContext(converter, type, culture), arg, out instance);
}


As you see – API references TypeConverter abstraction only.

Helper class (to reduce amount of arguments):

public class ConversionContext
{
    public ConversionContext(TypeConverter service, Type type, CultureInfo culture)
    {
        Service = service;
        Type = type;
        Culture = culture;
    }

    public TypeConverter Service { get; }
    public Type Type { get; }
    public CultureInfo Culture { get; }
}


Now simple immutable composite converter:

public class CompositeConverter : TypeConverter
{
    public static readonly TypeConverter Empty = new CompositeConverter();

    public CompositeConverter(params TypeConverter[] converters)
    {
        Converters = converters;
    }

    protected internal sealed override bool TryConvert(ConversionContext context, object arg, out object instance)
    {
        instance = null;
        foreach (var converter in Converters)
            if (converter.TryConvert(context, arg, out instance))
                return true;

        return false;
    }

    TypeConverter[] Converters { get; }
}


And a super-type for your business objects:

public abstract class TypeConverter : TypeConverter
{
    protected internal sealed override bool TryConvert(ConversionContext context, object arg, out object instance)
    {
        var match = context.Type.IsAssignableFrom(typeof(TResult)) && arg is TArg;
        instance = match ? (object)Convert((TArg)arg, context) : null;
        return match;
    }

    protected abstract TResult Convert(TArg arg, ConversionContext context);
}


UPDATE - using context.Service property

TypeConverter converter = TypeConverter.Default
            .Register()
            .Register();

        Console.WriteLine(
            converter.Convert(
                typeof(int[]), new[] { "42" }, CultureInfo.InvariantCulture));


where:

public class StringToInt32Converter : TypeConverter
{
    protected override int Convert(string arg, ConversionContext context) =>
        int.Parse(arg, context.Culture);
}

public class StringArrayToInt32ArrayConverter : TypeConverter
{
    protected override int[] Convert(string[] arg, ConversionContext context) =>
        arg
            .Select(s => (int)context.Service.Convert(typeof(int), s, context.Culture))
            .ToArray();
}

Code Snippets

class Program
{
    static void Main(string[] args)
    {
        TypeConverter converter = TypeConverter.Default
            .Register<StringToInt32Converter>();

        Console.WriteLine(
            converter.Convert(
                typeof(int), "42", CultureInfo.InvariantCulture));
    }
}
public class StringToInt32Converter : TypeConverter<string, int>
{
    protected override int Convert(string arg, ConversionContext context) =>
        int.Parse(arg, context.Culture);
}
public abstract class TypeConverter
    {
         public static readonly TypeConverter Default = CompositeConverter.Empty;
         protected internal abstract bool TryConvert(
             ConversionContext context, object arg, out object instance);
    }
public static class Composition
{
    public static TypeConverter Register<TConverter>(this TypeConverter that)
        where TConverter : TypeConverter, new() =>
        that.Register(new TConverter());

    // etc…

    // base method to be used above
    public static TypeConverter Register(this TypeConverter that, TypeConverter converter) =>
        new CompositeConverter(that, converter);
}
public static class Conversions
{
    public static object Convert(this TypeConverter converter, Type type, object arg, CultureInfo culture)
    {
        object instance;
        if (!converter.TryConvert(type, arg, culture, out instance))
            throw new NotSupportedException();

        return instance;
    }

    // etc

    // base method to be used above
    public static bool TryConvert(this TypeConverter converter, Type type, object arg, CultureInfo culture, out object instance) =>
        converter.TryConvert(new ConversionContext(converter, type, culture), arg, out instance);
}

Context

StackExchange Code Review Q#131784, answer score: 3

Revisions (0)

No revisions yet.