private static bool CanCastTo(this TypeInfo fromTypeInfo, TypeInfo toTypeInfo, FoundationTypes foundationTypes) { if (fromTypeInfo.Equals(toTypeInfo)) return true; if (fromTypeInfo.IsArray) { if (toTypeInfo.IsInterface) return fromTypeInfo.CanCastArrayToInterface(toTypeInfo, foundationTypes); Type toType = toTypeInfo.AsType(); if (fromTypeInfo.IsSubclassOf(toType)) return true; // T[] is castable to Array or Object. if (!toTypeInfo.IsArray) return false; int rank = fromTypeInfo.GetArrayRank(); if (rank != toTypeInfo.GetArrayRank()) return false; bool fromTypeIsSzArray = fromTypeInfo.IsSzArray(foundationTypes); bool toTypeIsSzArray = toTypeInfo.IsSzArray(foundationTypes); if (fromTypeIsSzArray != toTypeIsSzArray) { // T[] is assignable to T[*] but not vice-versa. if (!(rank == 1 && !toTypeIsSzArray)) { return false; // T[*] is not castable to T[] } } TypeInfo toElementTypeInfo = toTypeInfo.GetElementType().GetTypeInfo(); TypeInfo fromElementTypeInfo = fromTypeInfo.GetElementType().GetTypeInfo(); return fromElementTypeInfo.IsElementTypeCompatibleWith(toElementTypeInfo, foundationTypes); } if (fromTypeInfo.IsByRef) { if (!toTypeInfo.IsByRef) return false; TypeInfo toElementTypeInfo = toTypeInfo.GetElementType().GetTypeInfo(); TypeInfo fromElementTypeInfo = fromTypeInfo.GetElementType().GetTypeInfo(); return fromElementTypeInfo.IsElementTypeCompatibleWith(toElementTypeInfo, foundationTypes); } if (fromTypeInfo.IsPointer) { Type toType = toTypeInfo.AsType(); if (toType.Equals(foundationTypes.SystemObject)) return true; // T* is castable to Object. if (toType.Equals(foundationTypes.SystemUIntPtr)) return true; // T* is castable to UIntPtr (but not IntPtr) if (!toTypeInfo.IsPointer) return false; TypeInfo toElementTypeInfo = toTypeInfo.GetElementType().GetTypeInfo(); TypeInfo fromElementTypeInfo = fromTypeInfo.GetElementType().GetTypeInfo(); return fromElementTypeInfo.IsElementTypeCompatibleWith(toElementTypeInfo, foundationTypes); } if (fromTypeInfo.IsGenericParameter) { // // A generic parameter can be cast to any of its constraints, or object, if none are specified, or ValueType if the "struct" constraint is // specified. // // This has to be coded as its own case as TypeInfo.BaseType on a generic parameter doesn't always return what you'd expect. // Type toType = toTypeInfo.AsType(); if (toType.Equals(foundationTypes.SystemObject)) return true; if (toType.Equals(foundationTypes.SystemValueType)) { GenericParameterAttributes attributes = fromTypeInfo.GenericParameterAttributes; if ((attributes & GenericParameterAttributes.NotNullableValueTypeConstraint) != 0) return true; } foreach (Type constraintType in fromTypeInfo.GetGenericParameterConstraints()) { if (constraintType.GetTypeInfo().CanCastTo(toTypeInfo, foundationTypes)) return true; } return false; } if (toTypeInfo.IsArray || toTypeInfo.IsByRef || toTypeInfo.IsPointer || toTypeInfo.IsGenericParameter) return false; if (fromTypeInfo.MatchesWithVariance(toTypeInfo, foundationTypes)) return true; if (toTypeInfo.IsInterface) { foreach (Type ifc in fromTypeInfo.ImplementedInterfaces) { if (ifc.GetTypeInfo().MatchesWithVariance(toTypeInfo, foundationTypes)) return true; } return false; } else { // Interfaces are always castable to System.Object. The code below will not catch this as interfaces report their BaseType as null. if (toTypeInfo.AsType().Equals(foundationTypes.SystemObject) && fromTypeInfo.IsInterface) return true; TypeInfo walk = fromTypeInfo; for (;;) { Type baseType = walk.BaseType; if (baseType == null) return false; walk = baseType.GetTypeInfo(); if (walk.MatchesWithVariance(toTypeInfo, foundationTypes)) return true; } } }