/// <summary> /// Convert a current type to an expected type /// </summary> /// <param name="il">LazyILGenerator instance</param> /// <param name="actualType">Actual type</param> /// <param name="expectedType">Expected type</param> internal static void WriteTypeConversion(this LazyILGenerator il, Type actualType, Type expectedType) { var actualUnderlyingType = actualType.IsEnum ? Enum.GetUnderlyingType(actualType) : actualType; var expectedUnderlyingType = expectedType.IsEnum ? Enum.GetUnderlyingType(expectedType) : expectedType; if (actualUnderlyingType == expectedUnderlyingType) { return; } if (actualUnderlyingType.IsValueType) { if (expectedUnderlyingType.IsValueType) { // If both underlying types are value types then both must be of the same type. DuckTypeInvalidTypeConversionException.Throw(actualType, expectedType); } else { // An underlying type can be boxed and converted to an object or interface type if the actual type support this // if not we should throw. if (expectedUnderlyingType == typeof(object)) { // If the expected type is object we just need to box the value il.Emit(OpCodes.Box, actualType); } else if (expectedUnderlyingType.IsAssignableFrom(actualUnderlyingType)) { // If the expected type can be assigned from the value type (ex: struct implementing an interface) il.Emit(OpCodes.Box, actualType); il.Emit(OpCodes.Castclass, expectedType); } else { // If the expected type can't be assigned from the actual value type. // Means if the expected type is an interface the actual type doesn't implement it. // So no possible conversion or casting can be made here. DuckTypeInvalidTypeConversionException.Throw(actualType, expectedType); } } } else { if (expectedUnderlyingType.IsValueType) { // We only allow conversions from objects or interface type if the actual type support this // if not we should throw. if (actualUnderlyingType == typeof(object) || actualUnderlyingType.IsAssignableFrom(expectedUnderlyingType)) { // WARNING: The actual type instance can't be detected at this point, we have to check it at runtime. /* * In this case we emit something like: * { * if (!(value is [expectedType])) { * throw new InvalidCastException(); * } * * return ([expectedType])value; * } */ Label lblIsExpected = il.DefineLabel(); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Isinst, expectedType); il.Emit(OpCodes.Brtrue_S, lblIsExpected); il.Emit(OpCodes.Pop); il.ThrowException(typeof(InvalidCastException)); il.MarkLabel(lblIsExpected); il.Emit(OpCodes.Unbox_Any, expectedType); } else { DuckTypeInvalidTypeConversionException.Throw(actualType, expectedType); } } else if (expectedUnderlyingType != typeof(object)) { il.Emit(OpCodes.Castclass, expectedUnderlyingType); } } }