33

This is a follow-up to this question about converting values with reflection. Converting an object of a certain type to another type can be done like this:

object convertedValue = Convert.ChangeType(value, targetType);

Given two Type instances (say FromType and ToType), is there a way to test whether the conversion will succeed?

E.g. can I write an extension method like this:

public static class TypeExtensions
{
    public static bool CanChangeType(this Type fromType, Type toType)
    {
        // what to put here?
    }
}

EDIT: This is what I have right now. Ugly, but I don't see another way yet...

bool CanChangeType(Type sourceType, Type targetType)
{
  try
  {
    var instanceOfSourceType = Activator.CreateInstance(sourceType);
    Convert.ChangeType(instanceOfSourceType, targetType);
    return true; // OK, it can be converted
  }
  catch (Exception ex)
  {
    return false;
  }
Community
  • 1
  • 1
jeroenh
  • 26,362
  • 10
  • 73
  • 104
  • 4
    yeah, I'd love a Convert.TryChangeType method... – Thomas Levesque Sep 09 '09 at 12:22
  • @Thomas: that would be nice, but it is not exactly what I need here. I don't have an instance of fromType yet, just the Type itself. – jeroenh Sep 09 '09 at 12:24
  • 2
    I think all that you can reliably check is that fromType implements `IConvertible`, but that's no guarantee that any attempted conversion will actually succeed. – LukeH Sep 09 '09 at 12:40
  • I'm now executing the actual ChangeType method on an instance created via Activator.CreateInstance.Ugly, but I see no other way at this point... – jeroenh Sep 09 '09 at 13:53

4 Answers4

34

I was just encountering this same issue, and I used Reflector to look at the source for ChangeType. ChangeType throws exceptions in 3 cases:

  1. conversionType is null
  2. value is null
  3. value does not implement IConvertible

After those 3 are checked, it is guaranteed that it can be converted. So you can save a lot of performance and remove the try{}/catch{} block by simply checking those 3 things yourself:

public static bool CanChangeType(object value, Type conversionType)
{
    if (conversionType == null)
    {
        return false;
    }

    if (value == null)
    {              
        return false;
    }

    IConvertible convertible = value as IConvertible;

    if (convertible == null)
    {
        return false;
    }

    return true;
}
Edward Brey
  • 40,302
  • 20
  • 199
  • 253
esac
  • 24,099
  • 38
  • 122
  • 179
  • great answer. Should 've thought about it myself! – jeroenh Nov 04 '10 at 23:18
  • thanks. i left out one little check, but it is up to you. if convertible == null, but value is of the type you are trying to convert to already, then you should be able to return true. – esac Nov 05 '10 at 03:49
  • 46
    Be aware that it can still throw an InvalidCastException. And it seems like you can do nothing to precheck for this. It would be nice if IConvertible had an CanConvertToType method. – ctusch Jun 15 '12 at 10:27
  • 11
    The fact that an object implements IConvertible doesn't mean it can convert itself to any type. For instance, System.Int32 implements IConvertible, but I'm not sure if any Int32 can be converted to objects of class AfricanLanguage – Harald Coppoolse Dec 09 '13 at 15:26
  • Do not remove try/catch. Implementation can change and new exception can be thrown – Michael Freidgeim Mar 15 '17 at 21:26
  • What about System.FormatException ? – MemeDeveloper Jul 27 '17 at 00:04
  • 2
    There are other exceptions that may occur - if the two types are generally "compatible" but the source value will not fit in the destination; eg. Convert.ChangeType(double.MaxValue, typeof(int)) will throw an OverflowException. – Dan Roberts Apr 03 '19 at 21:00
  • 1
    This does not solve the problem at all. "abc" cannot be converted to a int even though both are IConvertable – Cesar Dec 04 '22 at 13:37
  • This answer should not be the accepted answer as it doesn't answer the OPs question. If you try to use convert of a string like "abc" into int, it will allow it without throwing an error since both are iConvertible. – PCoelho Mar 15 '23 at 21:19
  • @PCoelho / Cesar you are both missing the point of the question, and the purpose of the ChangeType method. Yes, the question asked if it WILL succeed, but ChangeType really is meant Can it succeed? If yes, try to do it. The Can portion checks if it is not null, or if it is IConvertible. Then it actually tries to do the conversion. Can a string "123" convert to int 123 - yes. Can a string "abc" convert to int? No, not until you actually attempt to do so. There are thousands of cases as detailed above where the actual conversion will fail and you can't check for them all except for a try/catch. – esac Mar 16 '23 at 22:14
9

Checking the method Convert.ChangeType in reflector I found this in the static constructor:

ConvertTypes = new Type[] { 
        typeof(Empty), typeof(object), typeof(DBNull), typeof(bool), typeof(char), typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal), 
        typeof(DateTime), typeof(object), typeof(string)
     };

In the end, this method is just checking either if the source is implementing IConvertible or if the target is one of the ConvertTypes above. So your method should look something like this (very rough):

return (ConvertTypes.Contains(toType) || typeof(IConvertible).IsAssignableFrom(fromType));
Marc Wittke
  • 2,991
  • 2
  • 30
  • 45
  • 5
    Similar to what I mentioned in my comment above. Unfortunately, checking that the `Type` implements `IConvertible` isn't enough. There's still no guarantee that any attempted conversion will actually succeed. – LukeH Sep 09 '09 at 12:47
  • In my scenario, I'm only concerned about these special primitives. I really just want to test that it's sane to attempt `ChangeType` and this is the only reasonable method (other than `try{...}catch{}` ) – Chris Marisic Jul 26 '16 at 21:38
  • 1
    Arguably it'd be better to use `HashSet` instead of `Type[]`, this is more important if you expect to hit this check in tight loops to avoid `O(N)` search in array. Calling `BinarySearch()` might be an option also – Chris Marisic Jul 26 '16 at 21:46
  • Again, this, like the other answer, is flat out wrong. Convert.ChangeType does NOT throw an exception when the target and source types are the same, regardless of whether or not it implements IConvertible. It also does not throw exceptions when the value is null, and the target type is not a value type. – Triynko Aug 24 '18 at 17:08
0

I have written a little framework that includes a Convert class that can do more than the System.Convert class. If you are interested in using it, you can download it from Github. It doesn't have the ability to determine if you can convert between values, but that seems like a good feature to add.

It does include the ability to convert values based on:

  • IConvertible
  • TypeConverters
  • ToXxx methods
  • Parse static methods
  • Parameterized constructor
  • and a few other minor ones

Data Type Conversions

Fabricio Godoy
  • 517
  • 6
  • 8
Brian
  • 37,399
  • 24
  • 94
  • 109
0

Based on what has already been answered on both questions I came up with this, (needs c#7)

public static object ChangeType(object value, Type conversion)
    {
        var type = conversion;

        if (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
        {
            if (value == null)
            {
                return null;
            }

            type = Nullable.GetUnderlyingType(type);
        }

        return Convert.ChangeType(value, type);
    }

public static (bool IsSuccess, object Value) TryChangeType(object value, Type conversionType)
    {
        (bool IsSuccess, object Value) response = (false, null);
        var isNotConvertible = 
            conversionType == null 
                || value == null 
                || !(value is IConvertible)
            || !(value.GetType() == conversionType);
        if (isNotConvertible)
        {
            return response;
        }
        try
        {
            response = (true, ChangeType(value, conversionType));
        }
        catch (Exception)
        {
            response.Value = null;
        }

        return response;
    }
}

Production code example:

public Item()
    {
        foreach (var pinfo in GetType().GetProperties())
        {
            object value = 0;
            var response = ReflectionHelpers.TryChangeType(value, pinfo.PropertyType);
            if(response.IsSuccess)
            {
                pinfo.SetValue(this, response.Value);
            }
        }
    }

This starts all properties possible with 0.

Immac
  • 466
  • 1
  • 5
  • 16