我有两种类型,T和U,我想知道是否从T到U定义了隐式转换运算符。
我知道IsAssignableFrom的存在,但这不是我要找的,因为它不能处理隐式转换。
一些谷歌搜索让我发现this solution,但根据作者自己的话,这是丑陋的代码(它试图隐式转型并在出现异常时返回false,否则返回true...)
似乎用正确签名的op_Implicit方法测试隐式转换操作符的存在won't work for primitive types。
有没有更简洁的方法来确定是否存在隐式转换操作符?
我有两种类型,T和U,我想知道是否从T到U定义了隐式转换运算符。
我知道IsAssignableFrom的存在,但这不是我要找的,因为它不能处理隐式转换。
一些谷歌搜索让我发现this solution,但根据作者自己的话,这是丑陋的代码(它试图隐式转型并在出现异常时返回false,否则返回true...)
似乎用正确签名的op_Implicit方法测试隐式转换操作符的存在won't work for primitive types。
有没有更简洁的方法来确定是否存在隐式转换操作符?
您可以使用反射来查找目标类型的隐式转换方法:
public static bool HasImplicitConversion(Type baseType, Type targetType)
{
return baseType.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == targetType)
.Any(mi => {
ParameterInfo pi = mi.GetParameters().FirstOrDefault();
return pi != null && pi.ParameterType == baseType;
});
}
你可以像这样使用它:
class X {}
class Y
{
public static implicit operator X (Y y)
{
return new X();
}
public static implicit operator Y (X x)
{
return new Y();
}
}
// and then:
bool conversionExists = HasImplicitConversion(typeof(Y), typeof(X));
请注意,这仅检查基本类型(第一个传递的类型)上的隐式类型转换。技术上,类型转换也可以定义在另一种类型上,因此您可能需要反转类型再次调用它(或将其构建到方法中)。但并非两种类型都存在隐式类型转换。
我最终手动处理了原始类型的情况。不是很优雅,但它能正常工作。
我还添加了额外的逻辑来处理可空类型和枚举。
我重用了Poke的代码来处理用户定义的类型场景。
public class AvailableCastChecker
{
public static bool CanCast(Type from, Type to)
{
if (from.IsAssignableFrom(to))
{
return true;
}
if (HasImplicitConversion(from, from, to)|| HasImplicitConversion(to, from, to))
{
return true;
}
List<Type> list;
if (ImplicitNumericConversions.TryGetValue(from, out list))
{
if (list.Contains(to))
return true;
}
if (to.IsEnum)
{
return CanCast(from, Enum.GetUnderlyingType(to));
}
if (Nullable.GetUnderlyingType(to) != null)
{
return CanCast(from, Nullable.GetUnderlyingType(to));
}
return false;
}
// https://msdn.microsoft.com/en-us/library/y5b434w4.aspx
static Dictionary<Type,List<Type>> ImplicitNumericConversions = new Dictionary<Type, List<Type>>();
static AvailableCastChecker()
{
ImplicitNumericConversions.Add(typeof(sbyte), new List<Type> {typeof(short), typeof(int), typeof(long), typeof(float), typeof(double), typeof(decimal) });
ImplicitNumericConversions.Add(typeof(byte), new List<Type> { typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) });
ImplicitNumericConversions.Add(typeof(short), new List<Type> { typeof(int), typeof(long), typeof(float), typeof(double), typeof(decimal) });
ImplicitNumericConversions.Add(typeof(ushort), new List<Type> { typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) });
ImplicitNumericConversions.Add(typeof(int), new List<Type> { typeof(long), typeof(float), typeof(double), typeof(decimal) });
ImplicitNumericConversions.Add(typeof(uint), new List<Type> { typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) });
ImplicitNumericConversions.Add(typeof(long), new List<Type> { typeof(float), typeof(double), typeof(decimal) });
ImplicitNumericConversions.Add(typeof(char), new List<Type> { typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal) });
ImplicitNumericConversions.Add(typeof(float), new List<Type> { typeof(double) });
ImplicitNumericConversions.Add(typeof(ulong), new List<Type> { typeof(float), typeof(double), typeof(decimal) });
}
static bool HasImplicitConversion(Type definedOn, Type baseType, Type targetType)
{
return definedOn.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == targetType)
.Any(mi =>
{
ParameterInfo pi = mi.GetParameters().FirstOrDefault();
return pi != null && pi.ParameterType == baseType;
});
}
}
public static bool IsImplicitFrom(this Type type, Type fromType) {
if (type == null || fromType == null) {
return false;
}
// support for reference type
if (type.IsByRef) { type = type.GetElementType(); }
if (fromType.IsByRef) { fromType = type.GetElementType(); }
// could always be convert to object
if (type.Equals(typeof(object))) {
return true;
}
// check if it could be convert using standard implicit cast
if (IsStandardImplicitFrom(type, fromType)) {
return true;
}
// determine implicit convert operator
Type nonNullalbeType, nonNullableFromType;
if (IsNullableType(type, out nonNullalbeType) &&
IsNullableType(fromType, out nonNullableFromType)) {
type = nonNullalbeType;
fromType = nonNullableFromType;
}
return ConversionCache.GetImplicitConversion(fromType, type) != null;
}
internal static bool IsStandardImplicitFrom(this Type type, Type fromType) {
// support for Nullable<T>
if (!type.IsValueType || IsNullableType(ref type)) {
fromType = GetNonNullableType(fromType);
}
// determine implicit value type convert
HashSet<TypeCode> typeSet;
if (!type.IsEnum &&
ImplicitNumericConversions.TryGetValue(Type.GetTypeCode(type), out typeSet)) {
if (!fromType.IsEnum && typeSet.Contains(Type.GetTypeCode(fromType))) {
return true;
}
}
// determine implicit reference type convert and boxing convert
return type.IsAssignableFrom(fromType);
}
更新: 这里是整个文件。
ImplicitNumericConversions
和ConversionCache
是从哪里来的? - Simon MᶜKenzie虽然Brann的解决方案适用于许多情况,但它未考虑非原始类型的隐式转换的级联效应。
例如:将float分配给Thickness(该Thickness从double进行了隐式转换)应返回true。
我重写了代码以处理这种情况。
public static class AvailableCastChecker
{
public static bool CanCast(Type from, Type to)
{
if (to.IsAssignableFrom(from))
{
return true;
}
if (HasImplicitConversion(from, from, to) || HasImplicitConversion(to, from, to))
{
return true;
}
if (ImplicitNumericConversions.TryGetValue(to, out var list) &&
(list.Contains(from) || list.Any(t => CanCast(from, t))))
{
return true;
}
if (to.IsEnum)
{
return CanCast(from, Enum.GetUnderlyingType(to));
}
return Nullable.GetUnderlyingType(to) != null && CanCast(from, Nullable.GetUnderlyingType(to));
}
// https://msdn.microsoft.com/en-us/library/y5b434w4.aspx
private static Dictionary<Type, List<Type>> _implicitNumericConversions;
private static Dictionary<Type, List<Type>> ImplicitNumericConversions => _implicitNumericConversions ?? (_implicitNumericConversions = new Dictionary<Type, List<Type>>()
{
{typeof(short), new List<Type> { typeof(sbyte), typeof(byte) }},
{typeof(ushort), new List<Type> { typeof(byte), typeof(char) }},
{typeof(int), new List<Type> { typeof(sbyte), typeof(byte), typeof(char), typeof(short), typeof(ushort) }},
{typeof(uint), new List<Type> { typeof(byte), typeof(char), typeof(ushort) }},
{typeof(long), new List<Type> { typeof(sbyte), typeof(byte), typeof(char), typeof(short), typeof(ushort), typeof(int), typeof(uint) }},
{typeof(ulong), new List<Type> { typeof(byte), typeof(char), typeof(ushort), typeof(uint) }},
{typeof(float), new List<Type> { typeof(sbyte), typeof(byte), typeof(char), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong) }},
{typeof(double), new List<Type> { typeof(sbyte), typeof(byte), typeof(char), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(float) }}
});
private static bool HasImplicitPrimitiveConversion(Type from, Type to)
{
return ImplicitNumericConversions.TryGetValue(to, out var list) && list.Contains(from);
}
private static bool HasImplicitConversion(Type definedOn, Type from, Type to)
{
return definedOn.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(mi => mi.Name == "op_Implicit"
&& (mi.ReturnType == to || HasImplicitPrimitiveConversion(from, to)))
.Any(mi =>
{
var pi = mi.GetParameters().FirstOrDefault();
return pi != null && (pi.ParameterType == from || HasImplicitPrimitiveConversion(from, pi.ParameterType));
});
}
}