c# - 隐式转换是如何实现的
问题描述
我正在创建一个带有 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
};
解决方案
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
源代码的抽象表示。这些不代表对象的实例,因此不能在运行时使用而不会严重滥用类。Type
ConversionsBase
有了这些信息,我们可以确定我最初的解决方法非常接近编译器在编译时确定隐式转换可用性的选择。
不幸的是,编译器中的这种实现对运行时使用几乎没有影响,至少在任何有意义的方面。
根据我的研究,我认为最好继续使用反射、表查找或二进制数学在运行时查找这些转换。
推荐阅读
- react-native - 错误:LAErrorAuthenticationFailed 身份验证无法启动,因为 Touch ID 没有注册手指。反应原生
- react-native - 在键盘模式下键入时反应原生的奇怪布局行为
- powerbi - 获取dax中子组的总数和最大值?
- csv - SwiftUI .fileExporter() 在视图中带有 .csv 文件
- r - 读取文本文件时遇到错误(内部错误:无效的头部位置)
- pandas - Pandas reshape Dataframe based on column value
- arrays - 在 C 中传递结构数组并在不同的函数中打印信息
- elasticsearch - Elasticsearch 是否支持索引别名的 TTL?
- node.js - 即使手动删除属性,文档验证也失败
- javascript - Storage.put() 在 React + AWS Amplify 应用程序中返回错误请求错误(状态代码 400)