Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to override NET type converters?

I'm working on an ETL-type application which creates entities from csv data files. One field in particular - a boolean field - is proving difficult to work with because systems provide their own interpretation of a bool such as true, false, yes, no, 1, 0 or even -1 etc.

Using the default type converters most of the tests fail:

var b1 = Convert.ChangeType("true", TypeCode.Boolean, CultureInfo.InvariantCulture);
var b2 = Convert.ChangeType("false", TypeCode.Boolean, CultureInfo.InvariantCulture);
var b3 = Convert.ChangeType("True", TypeCode.Boolean, CultureInfo.InvariantCulture);
var b4 = Convert.ChangeType("False", TypeCode.Boolean, CultureInfo.InvariantCulture);
var b5 = Convert.ChangeType("TRUE", TypeCode.Boolean, CultureInfo.InvariantCulture);
var b6 = Convert.ChangeType("FALSE", TypeCode.Boolean, CultureInfo.InvariantCulture);

// All below fail
var b7 = Convert.ChangeType("yes", TypeCode.Boolean, CultureInfo.InvariantCulture);
var b8 = Convert.ChangeType("no", TypeCode.Boolean, CultureInfo.InvariantCulture);
var b9 = Convert.ChangeType("Yes", TypeCode.Boolean, CultureInfo.InvariantCulture);
var b10 = Convert.ChangeType("No", TypeCode.Boolean, CultureInfo.InvariantCulture);
var b11 = Convert.ChangeType("YES", TypeCode.Boolean, CultureInfo.InvariantCulture);
var b12 = Convert.ChangeType("NO", TypeCode.Boolean, CultureInfo.InvariantCulture);
var b13 = Convert.ChangeType("1", TypeCode.Boolean, CultureInfo.InvariantCulture);
var b14 = Convert.ChangeType("0", TypeCode.Boolean, CultureInfo.InvariantCulture);

What I would like to do is override the default System.ComponentModel.BooleanConverter so that I can provide my own parser to correctly handle above. Any idea how to do this?

This post by Scott Hanselman touches on creating type converters but I wish to override the default one.

For reference here's my entity extractor implementation.

public static TEntity ExtractEntity<TEntity>(Dictionary<string, string> row)  where TEntity : class
{
    var entity = Activator.CreateInstance<TEntity>();
    var entityType = typeof(TEntity);

    foreach (var info in entityType.GetProperties())
    {
        try
        {
            info.SetValue(
                entity,
                Convert.ChangeType(row[info.Name], info.PropertyType, CultureInfo.InvariantCulture),
                null);
        }
        catch {}
    }

    return entity;
}

Basically it enumerates a given TEntity and for each public field it obtains the dictionary's item by its key and attempts to convert it to the field's underlying type. It is working well except for bools.

like image 601
Neil Dobson Avatar asked Sep 26 '13 02:09

Neil Dobson


People also ask

What is a typeconverter?

Type converters allow you to convert from your type to another type and also form another type back to your type. All type converters are inherited from the TypeConverter base class provided by .NET.

How many methods do I need to override when converting data?

You need to override four methods. The methods CanConvertTo and ConvertTo are used when converting from your type to another type, for example to a string. The methods CanConvertFrom and ConvertFrom are used when converting from another type back to your type, for example from a string.

How do I implement my own type converters?

This article explains how to implement your own type converters and how to associate those type converters with your complex types. The .NET framework makes it very easy to implement your own type converters by inheriting from the TypeConverter class and implementing the CanConvertTo, ConvertTo, CanConvertFrom and ConvertFrom methods.

What is an object type conversion?

Type conversion creates a value in a new type that is equivalent to the value of an old type, but does not necessarily preserve the identity (or exact value) of the original object. Conversion from a derived class to a base class. This means, for example, that an instance of any class or structure can be converted to an Object instance.


1 Answers

Thanks to Asad I created a custom TypeConverter

class BoolTypeConverter : TypeConverter
{
    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        if (destinationType == typeof (bool))
        {
            return true;
        }
        return base.CanConvertTo(context, destinationType);
    }

    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (value is string)
        {
            var s = value as string;
            if (string.IsNullOrEmpty(s))
                return false;
            switch (s.Trim().ToUpper())
            {
                case "TRUE":
                case "YES":
                case "1":
                case "-1":
                    return true;

                default:
                    return false;
            }
        }
        return base.ConvertTo(context, culture, value, destinationType);
    }
}

And registered in the program's startup:

TypeDescriptor.AddAttributes(typeof(Boolean),
new TypeConverterAttribute(typeof(BoolTypeConverter)));

Now with the modified extractor code, for each property the correct type converter is used. Normally this would be one of the built-in converters, but because of the registration of BoolTypeConverter for type boolean, this is used instead.

public static TEntity ExtractEntity<TEntity>(Dictionary<string, string> row)  where TEntity : class
{
    var entity = Activator.CreateInstance<TEntity>();
    var entityType = typeof(TEntity);

    foreach (var info in entityType.GetProperties())
    {
        try
        {
            var converter = TypeDescriptor.GetConverter(info.PropertyType);
            if (!converter.CanConvertTo(info.PropertyType)) continue;

            info.SetValue(entity, converter.ConvertTo(row[info.Name], info.PropertyType));
        }
        catch {}
    }

    return entity;
}
like image 173
Neil Dobson Avatar answered Sep 28 '22 14:09

Neil Dobson