如何在C#中检查类型是否可以转换为其他类型

本文关键字:类型 转换 其他 是否 检查 | 更新日期: 2023-09-27 18:22:27

我有两种类型sourceTypetargetType,我需要在C#中编写一个方法,该方法检查sourceType的值是否可以分配给targetType的变量。函数的签名为MatchResultTypeAndExpectedType(Type sourceType, Type targetType)

继承由IsAssignableFrom覆盖。在可转换类型的情况下,我想使用CanConvertFrom,但是,例如,如果两个类型都是数字类型,那么它总是返回false。我做了一个测试:

TypeConverter typeConverter = TypeDescriptor.GetConverter(typeof(Decimal));
Console.WriteLine("Int16 to Decimal - " + typeConverter.CanConvertFrom(typeof(Int16)));
Console.WriteLine("UInt16 to Decimal - " + typeConverter.CanConvertFrom(typeof(UInt16)));
typeConverter = TypeDescriptor.GetConverter(typeof(long));
Console.WriteLine("UInt16 to Int64 - " + typeConverter.CanConvertFrom(typeof(uint)));
typeConverter = TypeDescriptor.GetConverter(typeof(Double));
Console.WriteLine("UInt16 to Double - " + typeConverter.CanConvertFrom(typeof(UInt16)));
typeConverter = TypeDescriptor.GetConverter(typeof(String));
Console.WriteLine("UInt16 to String - " + typeConverter.CanConvertFrom(typeof(UInt16)));

结果是:

Int16 to Decimal - False
UInt16 to Decimal - False
UInt16 to Int64 - False
UInt16 to Double - False
UInt16 to String - False

[EDIT]所以我的问题是:在.NET中有没有一种方法可以检查给定类型的值是否可以在不知道值的情况下分配给另一类型的变量,例如,隐式转换是否会成功?MatchResultTypeAndExpectedType(Type sourceType, Type targetType):实施的更具体要求

  1. 源类型和目标类型在编译时是未知的,因为它们的程序集稍后加载
  2. 不能提供任何值或对象作为输入
  3. 在实现中不能创建任何类型的对象,因为系统的其他部分不允许这样做。可以创建值类型的值
  4. 只需要检查隐式转换

sourceType是否为值类型是已知的。因此,该方法的签名可以类似于MatchResultTypeAndExpectedType(Type sourceType, Boolean isSourceValueType, Type targetType)

一种方法是实现隐式数字转换表,但它不包括其他转换或用户定义的转换。

如何在C#中检查类型是否可以转换为其他类型

隐式/显式转换的问题在于它们在编译时得到解决。所以(据我所知)没有简单的运行时检查但是dynamic实现挑选它们并在运行时调用它们。您可以(无论多么丑陋)创建一个类,该类将尝试执行转换,如果失败则捕获异常,并报告它是否通过:

public class TypeConverterChecker<TFrom, TTo>
{
    public bool CanConvert { get; private set; }
    public TypeConverterChecker(TFrom from)
    {
        try
        {
            TTo to = (TTo)(dynamic)from;
            CanConvert = true;
        }
        catch
        {
            CanConvert = false;
        }
    }
}

给定一些类,如:

public class Foo
{
    public static implicit operator Bar(Foo foo)
    {
        return new Bar();
    }
    public static implicit operator Foo(Bar bar)
    {
        return new Foo();
    }
}
public class Bar
{
}
public class Nope
{
}

用法:

Console.WriteLine((new TypeConverterChecker<Foo, Bar>(new Foo())).CanConvert); //True
Console.WriteLine((new TypeConverterChecker<Bar, Foo>(new Bar())).CanConvert); //True
Console.WriteLine((new TypeConverterChecker<Foo, Nope>(new Foo())).CanConvert); //False

使用您测试的类型:

Console.WriteLine((new TypeConverterChecker<Int16, Decimal>(0)).CanConvert); //True
Console.WriteLine((new TypeConverterChecker<UInt16, Decimal>(0)).CanConvert); //True
Console.WriteLine((new TypeConverterChecker<UInt16, Int64>(0)).CanConvert); //True
Console.WriteLine((new TypeConverterChecker<UInt16, Double>(0)).CanConvert); //True
Console.WriteLine((new TypeConverterChecker<UInt16, String>(0)).CanConvert); //False

现在,我可以想象这可以被修改为更高效(静态缓存结果,这样相同TFrom, TTo组合的后续查找就不必尝试转换,因为值类型忽略了对输入实例进行强制转换的需要(只需使用default(TFrom))等等,但这应该会给你一个开始。需要注意的是,对于TFrom from,您应该而不是通过null,因为所有null转换都将通过(除非是值类型)

您还可以添加第二个try/catch来尝试使用Convert.ChangeType方法,并查看这些类型是否定义了可以利用的IConvertable实现。(您可能想将其存储为一个单独的布尔标志,这样您就可以知道以后需要执行哪种类型的转换)

编辑:如果你在编译时不知道类型,你可以利用一些反思来利用转换检查器:

public static class TypeConverterChecker
{
    public static bool Check(Type fromType, Type toType, object fromObject)
    {
        Type converterType = typeof(TypeConverterChecker<,>).MakeGenericType(fromType, toType);
        object instance = Activator.CreateInstance(converterType, fromObject);
        return (bool)converterType.GetProperty("CanConvert").GetGetMethod().Invoke(instance, null);
    }
}

您的用法可能如下:

object unknownObject = new Foo();
Type targetType = typeof(Bar);
Type sourceType = unknownObject.GetType();
Console.WriteLine(TypeConverterChecker.Check(sourceType, targetType, unknownObject));
targetType = typeof(Nope);
Console.WriteLine(TypeConverterChecker.Check(sourceType, targetType, unknownObject));

我实现了一个解决方案,它部分满足了我的需求。它基于@ChrisSinclair的答案,但不需要提供sourceType的对象。实现是:

    public static Boolean MatchResultTypeAndExpectedType(Type sourceType, Type targetType) {
        if (sourceType.IsValueType)
            return Check(sourceType, targetType);
        else
            return targetType.IsAssignableFrom(sourceType);
    }
    public static bool Check(Type fromType, Type toType) {
        Type converterType = typeof(TypeConverterChecker<,>).MakeGenericType(fromType, toType);
        object instance = Activator.CreateInstance(converterType);
        return (bool)converterType.GetProperty("CanConvert").GetGetMethod().Invoke(instance, null);
    }
    public class TypeConverterChecker<TFrom, TTo> {
        public bool CanConvert { get; private set; }
        public TypeConverterChecker() {
            TFrom from = default(TFrom);
            if (from == null)
                if (typeof(TFrom).Equals(typeof(String)))
                    from = (TFrom)(dynamic)"";
                else
                    from = (TFrom)Activator.CreateInstance(typeof(TFrom));
            try {
                TTo to = (dynamic)from;
                CanConvert = true;
            } catch {
                CanConvert = false;
            }
        }
    }

解决方案有两个问题:

  1. 它不检查是否存在非值类型(例如,用户定义的)的隐式转换。由于使用了IsAssignableFrom,因此仅对非值类型进行继承
  2. 它不包括值类型,默认值为null,没有默认构造函数,String除外。CCD_ 21被明确地覆盖

您总是可以这样做:

try
{
    Convert.ChangeType(val, typeof(targetType));
    return true;
}
catch (Exception)
{
    return false;
}

我意识到你没有这个例子,但你可以用轻松而廉价地创建它

var val = Activator.CreateInstance(sourceType);

请注意,Activator.CreateInstance()对于值类型来说速度极快。

当然,对于引用类型,只需使用type.IsInstanceOfType().