【问题标题】:How to check if type can be converted to another type in C#如何检查类型是否可以在C#中转换为另一种类型
【发布时间】:2013-07-14 15:38:02
【问题描述】:

我有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)

一种方法是实现Implicit Numeric Conversions Table,但它不会涵盖其他转换或用户定义的转换。

【问题讨论】:

  • 您应该更严格地定义“转换或分配给另一种数字类型”。当然,您可以将 long 转换/分配给 int,但您会丢失数据,因为 int64 比 int32 多 4 个字节。
  • 你是否也关心任何可能实现的implicit/explicit user defined conversion operators
  • 涵盖隐式用户定义的转换运算符会很棒。

标签: c# type-conversion


【解决方案1】:

关于隐式/显式转换的问题在于它们在编译时就已解决。所以(据我所知)没有简单的运行时检查。 然而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));

【讨论】:

  • 在我的例子中,源类型和目标类型可以作为变量使用,因为可以在应用程序运行时加载新类型。
  • @k_rus: 所以你在编译时不知道TFromTTo?没关系,您可以评估它们的类型并使用泛型在运行时创建泛型方法。我会更新我的答案。
  • 其实这是一个聪明的方法。
  • @Chris Sinclair:谢谢。我已经在研究如何在我的应用程序中使用这种方法。
  • 不需要提供from object到TypeConverterChecker,因为可以使用默认值,例如TFrom from = default(TFrom)
【解决方案2】:

我实施了一个解决方案,它部分满足了我的要求。它基于@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 除外。 String 被明确覆盖。

【讨论】:

    【解决方案3】:

    你总是可以这样做:

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

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

    var val = Activator.CreateInstance(sourceType);
    

    请注意,Activator.CreateInstance() 对于值类型非常快。

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

    【讨论】:

      猜你喜欢
      • 2012-08-16
      • 2011-06-07
      • 1970-01-01
      • 1970-01-01
      • 2020-12-02
      • 1970-01-01
      • 1970-01-01
      • 2012-04-11
      • 1970-01-01
      相关资源
      最近更新 更多