Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to check if type can be converted to another type in C#

I have two types sourceType and targetType and I need to write a method in C#, which checks if values of sourceType can be assigned to a variable of targetType. The signature of the function is MatchResultTypeAndExpectedType(Type sourceType, Type targetType).

The inheritance is covered by IsAssignableFrom. In the case of convertible types I thought to use CanConvertFrom, but, for example, if both types are numerical, then it always returns false. A test, which I performed:

TypeConverter typeConverter = TypeDescriptor.GetConverter(typeof(Decimal));
Console.WriteLine("Int16 to Decimal - " + typeConverter.CanConvertFrom(typeof(Int16)));
Console.WriteLine("UInt16 to Decimal - " + typeConverter.CanConvertFrom(typeof(UInt16)));

typeConverter = TypeDescriptor.GetConverter(typeof(long));
Console.WriteLine("UInt16 to Int64 - " + typeConverter.CanConvertFrom(typeof(uint)));

typeConverter = TypeDescriptor.GetConverter(typeof(Double));
Console.WriteLine("UInt16 to Double - " + typeConverter.CanConvertFrom(typeof(UInt16)));

typeConverter = TypeDescriptor.GetConverter(typeof(String));
Console.WriteLine("UInt16 to String - " + typeConverter.CanConvertFrom(typeof(UInt16)));

The result is:

Int16 to Decimal - False
UInt16 to Decimal - False
UInt16 to Int64 - False
UInt16 to Double - False
UInt16 to String - False

[EDIT] So my question: Is there a way in .NET to check whether a value of a given type can be assigned to a variable of another type without knowing values, e.g., whether implicit conversion will succeed? More specific requirements for implementation of MatchResultTypeAndExpectedType(Type sourceType, Type targetType):

  1. Source and target types are not known at compile time, since their assemblies are loaded later.
  2. No values or objects can be provided as input.
  3. No objects of the types can be created in the implementation, since it is not allowed by the rest of the system. Values of value types can be created.
  4. Only implicit conversion has to be checked.

It is known whether sourceType is value type. So the signature of the method can be like MatchResultTypeAndExpectedType(Type sourceType, Boolean isSourceValueType, Type targetType)

One way is to implement Implicit Numeric Conversions Table, but it will not cover other conversions or user defined conversions.

like image 661
k_rus Avatar asked Jul 16 '13 12:07

k_rus


2 Answers

The problem about implicit/explicit conversions is that they're resolved at compile time. So there is (as far as I know) no simple runtime check. However, the dynamic implementation will pick them out and invoke them at runtime. You can (however ugly) create a class which will attempt to perform the conversion, catch the exception if it fails, and report whether or not it passed:

public class TypeConverterChecker<TFrom, TTo>
{
    public bool CanConvert { get; private set; }

    public TypeConverterChecker(TFrom from)
    {
        try
        {
            TTo to = (TTo)(dynamic)from;
            CanConvert = true;
        }
        catch
        {
            CanConvert = false;
        }
    }
}

Given some classes like:

public class Foo
{
    public static implicit operator Bar(Foo foo)
    {
        return new Bar();
    }

    public static implicit operator Foo(Bar bar)
    {
        return new Foo();
    }
}

public class Bar
{
}

public class Nope
{

}

Usage:

Console.WriteLine((new TypeConverterChecker<Foo, Bar>(new Foo())).CanConvert); //True
Console.WriteLine((new TypeConverterChecker<Bar, Foo>(new Bar())).CanConvert); //True
Console.WriteLine((new TypeConverterChecker<Foo, Nope>(new Foo())).CanConvert); //False

And with the types you tested:

Console.WriteLine((new TypeConverterChecker<Int16, Decimal>(0)).CanConvert); //True
Console.WriteLine((new TypeConverterChecker<UInt16, Decimal>(0)).CanConvert); //True
Console.WriteLine((new TypeConverterChecker<UInt16, Int64>(0)).CanConvert); //True
Console.WriteLine((new TypeConverterChecker<UInt16, Double>(0)).CanConvert); //True
Console.WriteLine((new TypeConverterChecker<UInt16, String>(0)).CanConvert); //False

Now I can imagine this can be modified to be more efficient (cache the result statically so subsequent lookups for the same TFrom, TTo combination don't have to attempt the conversion, for value-types ignore the need for an input instance to cast (just use default(TFrom)) and so on, but it should give you a start. It should be noted that you should not pass in null for TFrom from as all null conversions will pass (unless it's to a value-type)

You can also add a second try/catch to attempt using the Convert.ChangeType method and see if the types have defined IConvertable implementations that can be leveraged. (you may want to store this as a separate boolean flag so you know which type of conversion you need to perform later)

EDIT: If you don't know the types at compile time, you can take advantage of a bit of reflection to still leverage the conversion checker:

public static class TypeConverterChecker
{
    public static bool Check(Type fromType, Type toType, object fromObject)
    {
        Type converterType = typeof(TypeConverterChecker<,>).MakeGenericType(fromType, toType);
        object instance = Activator.CreateInstance(converterType, fromObject);
        return (bool)converterType.GetProperty("CanConvert").GetGetMethod().Invoke(instance, null);
    }
}

Your usage might be like:

object unknownObject = new Foo();
Type targetType = typeof(Bar);
Type sourceType = unknownObject.GetType();
Console.WriteLine(TypeConverterChecker.Check(sourceType, targetType, unknownObject));

targetType = typeof(Nope);
Console.WriteLine(TypeConverterChecker.Check(sourceType, targetType, unknownObject));
like image 136
Chris Sinclair Avatar answered Sep 24 '22 02:09

Chris Sinclair


I implemented a solution, which partially satisfy my requirements. It is based on the answer from @ChrisSinclair, but does not require to provide an object of sourceType. The implementation is:

    public static Boolean MatchResultTypeAndExpectedType(Type sourceType, Type targetType) {
        if (sourceType.IsValueType)
            return Check(sourceType, targetType);
        else
            return targetType.IsAssignableFrom(sourceType);
    }

    public static bool Check(Type fromType, Type toType) {
        Type converterType = typeof(TypeConverterChecker<,>).MakeGenericType(fromType, toType);
        object instance = Activator.CreateInstance(converterType);
        return (bool)converterType.GetProperty("CanConvert").GetGetMethod().Invoke(instance, null);
    }

    public class TypeConverterChecker<TFrom, TTo> {
        public bool CanConvert { get; private set; }

        public TypeConverterChecker() {
            TFrom from = default(TFrom);
            if (from == null)
                if (typeof(TFrom).Equals(typeof(String)))
                    from = (TFrom)(dynamic)"";
                else
                    from = (TFrom)Activator.CreateInstance(typeof(TFrom));
            try {
                TTo to = (dynamic)from;
                CanConvert = true;
            } catch {
                CanConvert = false;
            }
        }
    }

There are two problems with the solution:

  1. It does not check if there are implicit conversions for non-value types (e.g., user defined). Since IsAssignableFrom is used, only inheritance is covered for non-value types.
  2. It does not cover value types, which has null value as default and does not have default constructors, except String. String is explicitly covered.
like image 34
k_rus Avatar answered Sep 23 '22 02:09

k_rus