首页 > 解决方案 > 隐式转换是如何实现的

问题描述

我正在创建一个带有 roslyn 和 C# 后端的实时解释脚本服务,并且需要确定任何给定的原语是否可以明确隐式地转换为任何其他给定的原语。

我在 IL 和 SO 中进行了搜索,发现了几篇文章,例如这篇文章,其中涵盖了解决方法。

rosyln / C# 如何确定任何给定的原语是否可以隐式转换为任何其他原语。IL 会让我相信它只是使用IConvertible包装到不同的转换函数中,但我觉得这会非常慢,因为它们的实现Convert.ChangeType抛出异常。

我已经实现了我自己的版本来检查原语之间的隐式转换(见下文),但我觉得我可能过于复杂了,并且存在一些方法来检查两个原语之间的隐式转换。

public static bool IsImplicitlyCastable(object Instance, Type DesiredType)
{
    // for convenience assume null can't be casted to non Nullable<T>
    if (Instance is null)
    {
        return false;
    }

    // cast the typecode of the instance to int
    int instanceTypeCode = (int)Type.GetTypeCode(Instance.GetType());

    // cast the typecode of the desired type to int
    int desiredTypeCode = (int)Type.GetTypeCode(DesiredType);

    // convert system typecode to BinaryTypeCode
    int desiredBinaryCode = 1 << (desiredTypeCode - 1);

    // determine if the instance is implicitly castable to the desired type, this was found to be 20% faster than a switch statement with constant integers
    return (desiredBinaryCode & Conversions[instanceTypeCode]) != 0;

}

public static readonly int[] Conversions = {
    0,
    0,
    0,
    4,
    32640,
    30032,
    32736,
    30016,
    32640,
    29952,
    32256,
    29696,
    30720,
    12288,
    8192,
    16384,
    0,
    0,
    0
};

标签: c#castingcompiler-constructionroslyn

解决方案


rosyln / C# 如何确定任何给定的原语是否可以隐式转换为任何其他原语。

Roslyn C# 编译器使用多维布尔数组来解析隐式和显式非托管(内置)转换。

在撰写本文时,当前的实现是由语义绑定器定义的——ConversionBase该类专门处理编译期间用户定义和非托管(内置)类型之间转换的语义。

每个 Binder/Semantics/Conversions/ConversionsBase.cs

// Licensed to the .NET Foundation

{ ... }

// Notice that there is no implicit numeric conversion from a type to itself. That's an
// identity conversion.
private static readonly bool[,] s_implicitNumericConversions =
{
            // to     sb  b  s  us i ui  l ul  c  f  d  m
            // from
            /* sb */
         { F, F, T, F, T, F, T, F, F, T, T, T },
            /*  b */
         { F, F, T, T, T, T, T, T, F, T, T, T },
            /*  s */
         { F, F, F, F, T, F, T, F, F, T, T, T },
            /* us */
         { F, F, F, F, T, T, T, T, F, T, T, T },
            /*  i */
         { F, F, F, F, F, F, T, F, F, T, T, T },
            /* ui */
         { F, F, F, F, F, F, T, T, F, T, T, T },
            /*  l */
         { F, F, F, F, F, F, F, F, F, T, T, T },
            /* ul */
         { F, F, F, F, F, F, F, F, F, T, T, T },
            /*  c */
         { F, F, F, T, T, T, T, T, F, T, T, T },
            /*  f */
         { F, F, F, F, F, F, F, F, F, F, T, F },
            /*  d */
         { F, F, F, F, F, F, F, F, F, F, F, F },
            /*  m */
         { F, F, F, F, F, F, F, F, F, F, F, F }
        };

private static readonly bool[,] s_explicitNumericConversions =
{
            // to     sb  b  s us  i ui  l ul  c  f  d  m
            // from
            /* sb */
         { F, T, F, T, F, T, F, T, T, F, F, F },
            /*  b */
         { T, F, F, F, F, F, F, F, T, F, F, F },
            /*  s */
         { T, T, F, T, F, T, F, T, T, F, F, F },
            /* us */
         { T, T, T, F, F, F, F, F, T, F, F, F },
            /*  i */
         { T, T, T, T, F, T, F, T, T, F, F, F },
            /* ui */
         { T, T, T, T, T, F, F, F, T, F, F, F },
            /*  l */
         { T, T, T, T, T, T, F, T, T, F, F, F },
            /* ul */
         { T, T, T, T, T, T, T, F, T, F, F, F },
            /*  c */
         { T, T, T, F, F, F, F, F, F, F, F, F },
            /*  f */
         { T, T, T, T, T, T, T, T, T, F, F, T },
            /*  d */
         { T, T, T, T, T, T, T, T, T, T, F, T },
            /*  m */
         { T, T, T, T, T, T, T, T, T, T, T, F }
        };

private static int GetNumericTypeIndex(SpecialType specialType)
{
    switch (specialType)
    {
        case SpecialType.System_SByte: return 0;
        case SpecialType.System_Byte: return 1;
        case SpecialType.System_Int16: return 2;
        case SpecialType.System_UInt16: return 3;
        case SpecialType.System_Int32: return 4;
        case SpecialType.System_UInt32: return 5;
        case SpecialType.System_Int64: return 6;
        case SpecialType.System_UInt64: return 7;
        case SpecialType.System_Char: return 8;
        case SpecialType.System_Single: return 9;
        case SpecialType.System_Double: return 10;
        case SpecialType.System_Decimal: return 11;
        default: return -1;
    }
}

#nullable enable
private static bool HasImplicitNumericConversion(TypeSymbol source, TypeSymbol destination)
{
    Debug.Assert((object)source != null);
    Debug.Assert((object)destination != null);

    int sourceIndex = GetNumericTypeIndex(source.SpecialType);
    if (sourceIndex < 0)
    {
        return false;
    }

    int destinationIndex = GetNumericTypeIndex(destination.SpecialType);
    if (destinationIndex < 0)
    {
        return false;
    }

    return s_implicitNumericConversions[sourceIndex, destinationIndex];
}

private static bool HasExplicitNumericConversion(TypeSymbol source, TypeSymbol destination)
{
    // SPEC: The explicit numeric conversions are the conversions from a numeric-type to another 
    // SPEC: numeric-type for which an implicit numeric conversion does not already exist.
    Debug.Assert((object)source != null);
    Debug.Assert((object)destination != null);

    int sourceIndex = GetNumericTypeIndex(source.SpecialType);
    if (sourceIndex < 0)
    {
        return false;
    }

    int destinationIndex = GetNumericTypeIndex(destination.SpecialType);
    if (destinationIndex < 0)
    {
        return false;
    }

    return s_explicitNumericConversions[sourceIndex, destinationIndex];
}

需要注意的重要一点是,从我能够从源代码中得出的信息是,它使用的是特定成员或表达式主体中TypeSymbol源代码的抽象表示。这些不代表对象的实例,因此不能在运行时使用而不会严重滥用类。TypeConversionsBase

有了这些信息,我们可以确定我最初的解决方法非常接近编译器在编译时确定隐式转换可用性的选择。

不幸的是,编译器中的这种实现对运行时使用几乎没有影响,至少在任何有意义的方面。

根据我的研究,我认为最好继续使用反射、表查找或二进制数学在运行时查找这些转换。


推荐阅读