隐式转换是如何实现的

How is implicit casting implemented

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

我搜索了 IL 和 SO,发现了一些帖子,例如 this one 涵盖了解决方法。

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
};

How does rosyln / C# determine if any given primitive is implicitly castable to any other primitive.

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

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

Per 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,它是特定成员中源代码 Type 的抽象表示,或者表达体。 这些不代表对象的实例,因此不能在 运行 期间使用而不严重滥用 ConversionsBase class。

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

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

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