Ejemplo n.º 1
0
    /// <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);
            }
        }
    }