【问题标题】:Check if types are castable / subclasses检查类型是否可转换/子类
【发布时间】:2011-01-08 07:59:17
【问题描述】:

我将它们的两个成员类型作为字符串 - 而不是作为 Type 实例。如何检查这两种类型是否可转换?假设第一个字符串是“System.Windows.Forms.Label”,另一个是“System.Windows.Forms.Control”。如何检查第一个是否是第二个的子类(或隐式可转换)?这可以通过使用反射来实现吗?

感谢您的支持!

【问题讨论】:

  • 这些字符串从何而来?你想完成什么?

标签: c#


【解决方案1】:

您似乎应该使用Type.IsAssignableFrom,但请仔细注意文档:

public virtual bool IsAssignableFrom(Type c)

true 如果c 和当前[instance of] Type 表示相同类型,或者如果当前[instance of] Typec 的继承层次结构中,或者如果当前[实例] Typec 实现的接口,或者如果c 是泛型类型参数并且当前[实例] Type 表示c 的约束之一。 false 如果这些条件都不是true,或者cnull 引用(在Visual Basic 中为Nothing)。

特别是:

class Base { }
clase NotABase { public static implicit operator Base(NotABase o) { // } }

Console.WriteLine(typeof(Base).IsAssignableFrom(typeof(NotABase)));

将在控制台上打印False,即使NotABases 可以隐式转换为Bases。所以,为了处理投射,我们可以像这样使用反射:

static class TypeExtensions {
    public static bool IsCastableTo(this Type from, Type to) {
        if (to.IsAssignableFrom(from)) {
            return true;
        }
        return from.GetMethods(BindingFlags.Public | BindingFlags.Static)
                          .Any(
                              m => m.ReturnType == to && 
                                   (m.Name == "op_Implicit" || 
                                    m.Name == "op_Explicit")
                          );
    }
}

用法:

Console.WriteLine(typeof(string).IsCastableTo(typeof(int))); // false
Console.WriteLine(typeof(NotABase).IsCastableTo(typeof(Base))); // true

对于你的情况

// from is string representing type name, e.g. "System.Windows.Forms.Label"
// to is string representing type name, e.g. "System.Windows.Forms.Control"
Type fromType = Type.GetType(from);
Type toType = Type.GetType(to);
bool castable = from.IsCastableTo(to);

【讨论】:

  • typeof(short).IsCastableTo(typeof(int)) 返回false,即使short 可以隐式转换为int。有没有办法让你的方法也适用于这种情况?
  • 很好的解决方案。为了完整起见,我相信您应该检查 m.ReturnType.IsAssignable(to) 而不是检查相等性。这意味着您将获得返回更多派生类型的隐式运算符。一个罕见的案例,但值得把它扔在那里。
  • @Jason,这里有一个小问题,您应该循环遍历两种类型,因为可以在任何一种类型上定义强制转换运算符。正如 Gusdor 所说,IsAssignableFrom 将通过继承处理可能的演员表。尽管如此,还是一个出色的首发。我会尝试扩展它。
  • @Gusdor 在我看来这并不罕见。感谢您的建议,我已经回答了..
【解决方案2】:

这次讨论对我有帮助,谢谢。

我修改了nawfal的代码来解决原始类型的问题。

现在它返回正确的结果。

typeof(short).IsCastableTo(typeof(int)); // True
typeof(short).IsCastableTo(typeof(int), implicitly:true); // True
typeof(int).IsCastableTo(typeof(short)); // True
typeof(int).IsCastableTo(typeof(short), implicitly:true); // False

代码如下。

public static bool IsCastableTo(this Type from, Type to, bool implicitly = false)
{
    return to.IsAssignableFrom(from) || from.HasCastDefined(to, implicitly);
}

static bool HasCastDefined(this Type from, Type to, bool implicitly)
{
    if ((from.IsPrimitive || from.IsEnum) && (to.IsPrimitive || to.IsEnum))
    {
        if (!implicitly)
            return from==to || (from!=typeof(Boolean) && to!=typeof(Boolean));

        Type[][] typeHierarchy = {
            new Type[] { typeof(Byte),  typeof(SByte), typeof(Char) },
            new Type[] { typeof(Int16), typeof(UInt16) },
            new Type[] { typeof(Int32), typeof(UInt32) },
            new Type[] { typeof(Int64), typeof(UInt64) },
            new Type[] { typeof(Single) },
            new Type[] { typeof(Double) }
        };
        IEnumerable<Type> lowerTypes = Enumerable.Empty<Type>();
        foreach (Type[] types in typeHierarchy)
        {
            if ( types.Any(t => t == to) )
                return lowerTypes.Any(t => t == from);
            lowerTypes = lowerTypes.Concat(types);
        }

        return false;   // IntPtr, UIntPtr, Enum, Boolean
    }
    return IsCastDefined(to, m => m.GetParameters()[0].ParameterType, _ => from, implicitly, false)
        || IsCastDefined(from, _ => to, m => m.ReturnType, implicitly, true);
}

static bool IsCastDefined(Type type, Func<MethodInfo, Type> baseType,
                        Func<MethodInfo, Type> derivedType, bool implicitly, bool lookInBase)
{
    var bindinFlags = BindingFlags.Public | BindingFlags.Static
                    | (lookInBase ? BindingFlags.FlattenHierarchy : BindingFlags.DeclaredOnly);
    return type.GetMethods(bindinFlags).Any(
        m => (m.Name=="op_Implicit" || (!implicitly && m.Name=="op_Explicit"))
            && baseType(m).IsAssignableFrom(derivedType(m)));
}

【讨论】:

    【解决方案3】:

    这与杰森的回答相同,但用他的解决方案解决了一些问题。

    public static bool IsCastableTo(this Type from, Type to)
    {
        return to.IsAssignableFrom(from)
            || to.GetConvertOperators().Any(m => m.GetParameters()[0].ParameterType.IsAssignableFrom(from))
            || from.GetConvertOperators(true).Any(m => to.IsAssignableFrom(m.ReturnType));
    }
    
    public static IEnumerable<MethodInfo> GetConvertOperators(this Type type, bool lookInBase = false)
    {
        var bindinFlags = BindingFlags.Public
                        | BindingFlags.Static
                        | (lookInBase ? BindingFlags.FlattenHierarchy : BindingFlags.DeclaredOnly);
        return type.GetMethods(bindinFlags).Where(m => m.Name == "op_Implicit" || m.Name == "op_Explicit");
    }
    

    这也应该处理由于继承而出现的情况。例如:

    class Mammal { public static implicit operator Car (Mammal o) { return null; } }
    class Cow : Mammal { }
    class Vehicle { }
    class Car : Vehicle { }
    

    这里隐式关系在MammalCar之间,但由于Cow也是Mammal,所以存在从CowCar的隐式转换。但是所有Cars 都是Vehicles;因此Cow 会变成Vehicle

    Cow c = null; 
    Vehicle v = c; //legal
    

    所以

    typeof(Cow).IsCastableTo(typeof(Vehicle)); //true 
    

    打印为 true,即使 CowVehicle 之间不存在直接转换运算符。

    上面的解决方案对于转换直接内置于语言而不是框架的原始类型失败,所以类似于

    typeof(short).IsCastableTo(typeof(int));
    

    失败。 Afaik,只有手动处理会有所帮助。您将从 msdn 获得 implicitexplicit 数字类型和 other primitive types 转换的完整列表。

    编辑:

    IsCastableTo 函数可能会稍微多一点“DRY”可能会降低可读性,但我喜欢它 :)

    public static bool IsCastableTo(this Type from, Type to)
    {
        return to.IsAssignableFrom(from)
            || IsCastDefined(to, m => m.GetParameters()[0].ParameterType, _ => from, false)
            || IsCastDefined(from, _ => to, m => m.ReturnType, true);
    }
    
    //little irrelevant DRY method
    static bool IsCastDefined(Type type, Func<MethodInfo, Type> baseType, Func<MethodInfo, Type> derivedType, 
                              bool lookInBase)
    {
        var bindinFlags = BindingFlags.Public
                        | BindingFlags.Static
                        | (lookInBase ? BindingFlags.FlattenHierarchy : BindingFlags.DeclaredOnly);
        return type.GetMethods(bindinFlags).Any(m => (m.Name == "op_Implicit" || m.Name == "op_Explicit") 
                                                  && baseType(m).IsAssignableFrom(derivedType(m)));
    }
    

    【讨论】:

    • 干是什么意思?
    • 另外,int short or long 还是不能解决问题
    • @LouisRhys 干点指向en.wikipedia.org/wiki/Don't_repeat_yourself,我已在答案中进行了更新。在我的第一个答案中,我使用IsAssignableFrom 功能两次或多或少地做同样的事情。在第二种方法中,我将其重构为单一方法。那里有点笨拙,但我更喜欢它。
    • @LouisRhys 我在回答中提到了这一点。我将其更新为以粗体突出显示。但是你可以处理它。通过检测原始类型自己进行一些手动处理。我发布了两个链接,您将在其中获得完整列表。如果您有时间,请务必将其发布为答案 :) 就我而言,我可以放心地忽略它们。如果将来有空闲时间,我也会更新以涵盖该问题。。
    【解决方案4】:

    最简单的方法是 value.GetType().IsSubclassOf(typeof(Control)) 基本上,Type.IsSubclassOf 方法可以满足您的需要

    【讨论】:

      【解决方案5】:

      怎么样:

      public bool IsCastable(String type0, String type1)
      {
        return Type.GetType(type1).IsAssignableFrom(Type.GetType(type0));
      }
      

      【讨论】:

      • 需要注意的是,这实际上并不检查type0所代表的类型是否可以转换为type1所代表的类型。即使NotABase 可以转换为Base,您的方法也会为Base { } class NotABase { public static implicit operator Base(NotABase o) { // details } } 类打印false。由于问题要求处理可能进行隐式转换的情况,因此我投反对票。
      【解决方案6】:

      如果您可以将这些字符串转换为Type 对象,那么您最好的选择是Type.IsAssignableFrom

      但请注意,这只会告诉您两个 Type 实例是否在 CLR 级别兼容。这不会考虑诸如用户定义的转换或其他 C# 语义之类的事情。

      【讨论】:

        猜你喜欢
        • 2013-05-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-07-14
        • 2021-04-05
        • 2011-06-07
        相关资源
        最近更新 更多