如何在C#中检查类型是否可以转换为另一种类型

9

我有两种类型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

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

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

已知sourceType是否为值类型。 因此,这个方法的签名可以像这样:MatchResultTypeAndExpectedType(Type sourceType, Boolean isSourceValueType, Type targetType)

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


3
您应该更严格地定义“转换或分配为其他数字类型”。当然,您可以将long转换/分配为int,但是您会因为int64比int32多4个字节而丢失数据。 - crush
1
你是否也关心可能被实现的任何隐式/显式用户定义转换运算符 - Chris Sinclair
覆盖隐式用户定义转换运算符会很棒。 - k_rus
3个回答

10

隐式/显式转换的问题在于它们在编译时解决。据我所知,没有简单的运行时检查。然而,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))等等,但这应该给你一个开始。需要注意的是,您不应该将 null 传递给 TFrom from,因为所有的 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));

1
在我的情况下,源类型和目标类型是可用的变量,因为新类型可以在应用程序运行时加载。 - k_rus
@k_rus:所以你在编译时不知道TFromTTo?没关系,您可以评估它们的类型并使用泛型在运行时创建通用方法。我会更新我的答案。 - Chris Sinclair
这实际上是一个聪明的方法。 - crush
@Chris Sinclair:谢谢。我已经在寻找如何在我的应用程序中采用这种方法了。 - k_rus
不必提供从对象到“TypeConverterChecker”的转换,因为可以使用默认值,例如TFrom from = default(TFrom) - k_rus
如果TFrom是引用类型(即class而不是struct),那么default(TFrom)将是null。任何从一个引用类型到另一个引用类型的null转换都会通过,从而给出错误的结果。仅当TFromstruct时,使用default(TFrom)才会提供有效的结果。我已经在我的答案中注意到了这个警告。不过你可以随意创建一个class StructTypeConverterChecker<T> where T : struct来解决这个问题! :) - Chris Sinclair

1
我实现了一个方案,部分满足我的要求。它基于@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. 它不包括具有空值作为默认值且没有默认构造函数的值类型,除了StringString已经明确涵盖。

0

你总是可以这样做:

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

我知道您没有实例,但是您可以轻松且非常便宜地通过以下方式创建实例:

var val = Activator.CreateInstance(sourceType);

请注意,对于值类型,Activator.CreateInstance()非常快速。
当然,对于引用类型,只需使用Type.IsInstanceOfType()。

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