patterncsharpMinor
Type converter framework (v2)
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:
Base class for all converters:
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)
{
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 :)
Here is an example. Demo code:
Where:
Now library. Let’s define our abstraction as clean as possible:
And all the API with multiple overloads will come as two sets of extension methods – I really like this trick :)
And:
As you see – API references
Helper class (to reduce amount of arguments):
Now simple immutable composite converter:
And a super-type for your business objects:
UPDATE - using
where:
- 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, notCompositeConverterto 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 propertyTypeConverter 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.