如何确定在C#中是否存在隐式转换?

27

我有两种类型,T和U,我想知道是否从T到U定义了隐式转换运算符。

我知道IsAssignableFrom的存在,但这不是我要找的,因为它不能处理隐式转换。

一些谷歌搜索让我发现this solution,但根据作者自己的话,这是丑陋的代码(它试图隐式转型并在出现异常时返回false,否则返回true...)

似乎用正确签名的op_Implicit方法测试隐式转换操作符的存在won't work for primitive types

有没有更简洁的方法来确定是否存在隐式转换操作符?


只是一个提示:查看[隐式类型转换运算符](https://msdn.microsoft.com/en-us/library/z5z9kes2.aspx)。我猜应该有一种通过反射找到隐式运算符的方法... - user2819245
你能详细说明一下你希望通过这个结果实现什么目标吗?我之前遇到过类似的问题,意识到它基本上必须看起来像CliveDM在下面链接中提到的那样,并决定只需调用Convert.ChangeType并处理异常。我意识到这可能不是你的情况下可行的解决方案,但也许有类似的变通方法。 - Joe Amenta
4个回答

19

您可以使用反射来查找目标类型的隐式转换方法:

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

请注意,这仅检查基本类型(第一个传递的类型)上的隐式类型转换。技术上,类型转换也可以定义在另一种类型上,因此您可能需要反转类型再次调用它(或将其构建到方法中)。但并非两种类型都存在隐式类型转换。


似乎对于原始类型无效,例如MappingsGetter.HasImplicitConversion(typeof(int), typeof(decimal))返回false,而实际上存在隐式转换:https://msdn.microsoft.com/en-us/library/y5b434w4.aspx - Brann
4
@Brann那些类型没有隐式转换符;类型转换是由CLR直接完成的。然而它们确实实现了IConvertible,所以你可以测试一下是否有实现该接口。 - poke
@Brann 对于原始类型,您将不得不在某个地方硬编码一个表。 我不知道有任何内置的机制可以让您以编程方式执行此操作。 - Kyle
@Kyle:我刚刚完成了这个并将其发布为答案。虽然不够优雅,但它能胜任工作。 - Brann

7

我最终手动处理了原始类型的情况。不是很优雅,但它能正常工作。

我还添加了额外的逻辑来处理可空类型和枚举。

我重用了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;
            });

    }
}

枚举类型的处理方式令人惊讶,因为它似乎不遵循与其他所有内容相同的规则。请参见http://hastebin.com/rexuzaraju以获取详细信息。也许这没关系,但至少原始问题中漏掉了这个问题。 - Joe Amenta
在字典中使用的正确类型是 HashSet。 - Mauro Sampietro
“if (from.IsAssignableFrom(to))” 这个条件语句不应该是 “if (to.IsAssignableFrom(from))” 吗?我意识到这是一个常见的错误/混淆来源(证据:参见 IsAssignableTo 方法的建议并观察 .NET 6 api 集中是否存在此成员) …… 我是错了,还是代码错了? - kkahl

3
我找到了一个解决方案,这里是链接。重要的代码如下(经过简单翻译):
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);
}

更新: 这里是整个文件。


如果(fromType.IsByRef){ fromType = type.GetElementType(); } 调用了错误变量的 GetElementType。 - Nikon the Third
1
ImplicitNumericConversionsConversionCache是从哪里来的? - Simon MᶜKenzie
@CliveDM,可能你已经修复了“整个文件”中的ImplicitNumericConversions引用。但是ConversionCache仍然是一个悬空引用。 - AviFarah

0

虽然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));
            });
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接