Pergunta

Edited after reading further, modified question to be more specific.

As per Microsoft documentation:

An is expression evaluates to true if the provided expression is non-null, and the provided object can be cast to the provided type without causing an exception to be thrown. Otherwise, the expression evaluates to false.

Here is the issue below.

var test = (Int32)(Int16)1; // Non-null and does not cause an exception.
var test2 = (Int16)1 is Int32; // Evaluates false.

The documentation also states:

Note that the is operator only considers reference conversions, boxing conversions, and unboxing conversions. Other conversions, such as user-defined conversions, are not considered.

So, I'd assume that it doesn't work above because it is a user-defined conversion.

How would I check to see if something is castable to another type including non-reference/boxing/unboxing conversions?

Note: I found this issue when writing unit tests for my CastToOrDefault extension that works on all types, including non-reference types (as compared to as).


Refactored Answer based on Mike Precup's linked code

I cleaned up his answer because I felt it was written in an old style. Plus, even though it is slower, it felt kinda wonky to have a table with many repeated values, when each list is simply a subset of another list. So, I made the table recursive instead. Note: ThrowIfNull simply throws a ArgumentNullException if the value is null.

private static readonly Dictionary<Type, IEnumerable<Type>> PrimitiveTypeTable = new Dictionary<Type, IEnumerable<Type>>
{
    { typeof(decimal), new[] { typeof(long), typeof(ulong) } },
    { typeof(double), new[] { typeof(float) } },
    { typeof(float), new[] { typeof(long), typeof(ulong) } },
    { typeof(ulong), new[] { typeof(uint) } },
    { typeof(long), new[] { typeof(int), typeof(uint) } },
    { typeof(uint), new[] { typeof(byte), typeof(ushort) } },
    { typeof(int), new[] { typeof(sbyte), typeof(short), typeof(ushort) } },
    { typeof(ushort), new[] { typeof(byte), typeof(char) } },
    { typeof(short), new[] { typeof(byte) } }
};

private static bool IsPrimitiveCastableTo(this Type fromType, Type toType)
{
    var keyTypes = new Queue<Type>(new[] { toType });
    while (keyTypes.Any())
    {
        var key = keyTypes.Dequeue();
        if (key == fromType) { return true; }
        if (PrimitiveTypeTable.ContainsKey(key)) { PrimitiveTypeTable[key].ToList().ForEach(keyTypes.Enqueue); }
    }
    return false;
}

/// <summary>
/// Determines if this type is castable to the toType.
/// This method does more than the is-operator and
/// allows for primitives and implicit/explicit conversions to be compared properly.
/// http://stackoverflow.com/a/18256885/294804
/// </summary>
/// <param name="fromType">The type to cast from.</param>
/// <param name="toType">The type to be casted to.</param>
/// <returns>True if fromType can be casted to toType. False otherwise.</returns>
/// <exception cref="ArgumentNullException">Thrown if either type is null.</exception>
public static bool IsCastableTo(this Type fromType, Type toType)
{
    // http://stackoverflow.com/a/10416231/294804
    return toType.ThrowIfNull().IsAssignableFrom(fromType.ThrowIfNull()) ||
        fromType.IsPrimitiveCastableTo(toType) ||
        fromType.GetMethods(BindingFlags.Public | BindingFlags.Static).Any(m =>
                m.ReturnType == toType && m.Name == "op_Implicit" || m.Name == "op_Explicit");
}
Foi útil?

Solução

Also from the MSDN page you linked:

Note that the is operator only considers reference conversions, boxing conversions, and unboxing conversions. Other conversions, such as user-defined conversions, are not considered.

Since the implicit cast defined for the Int16 and Int32 types isn't a reference conversion, boxing conversion, or unboxing conversion, is evaluates to false.

If you're wondering why it isn't a reference conversion, note the following from the page on implicit reference conversions:

Reference conversions, implicit or explicit, never change the referential identity of the object being converted. In other words, while a reference conversion may change the type of the reference, it never changes the type or value of the object being referred to.

Since the type is being changed, and it's not simply a cast to a superclass, it's not considered a reference conversion.

EDIT: To answer the second question you edited in, view this page. I'll copy the code for reference:

static class TypeExtensions { 
    static Dictionary<Type, List<Type>> dict = new Dictionary<Type, List<Type>>() {
        { typeof(decimal), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char) } },
        { typeof(double), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } },
        { typeof(float), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char) } },
        { typeof(ulong), new List<Type> { typeof(byte), typeof(ushort), typeof(uint), typeof(char) } },
        { typeof(long), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(char) } },
        { typeof(uint), new List<Type> { typeof(byte), typeof(ushort), typeof(char) } },
        { typeof(int), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(char) } },
        { typeof(ushort), new List<Type> { typeof(byte), typeof(char) } },
        { typeof(short), new List<Type> { typeof(byte) } }
    };
    public static bool IsCastableTo(this Type from, Type to) { 
        if (to.IsAssignableFrom(from)) { 
            return true; 
        }
        if (dict.ContainsKey(to) && dict[to].Contains(from)) {
            return true;
        }
        bool castable = from.GetMethods(BindingFlags.Public | BindingFlags.Static) 
                        .Any( 
                            m => m.ReturnType == to &&  
                            m.Name == "op_Implicit" ||  
                            m.Name == "op_Explicit" 
                        ); 
        return castable; 
    } 
} 

The code uses Reflection to check if any implicit or explicit casts exist. The reflection method doesn't work on primitives, though, so the check must be done manually, hence the Dictionary with possible casts for primitives.

Outras dicas

As per MSDN also -

The is keyword causes a compile-time warning if the expression is known to always be true or to always be false, but typically evaluates type compatibility at run time.

And it can easily be seen in the warning -

Warning

And @Mike has already pointed out that why it evaluated to be false.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top