简体   繁体   English

将 IsAssignableFrom 与“开放”泛型类型一起使用

[英]Using IsAssignableFrom with 'open' generic types

Using reflection, I'm attempting to find the set of types which inherit from a given base class.使用反射,我试图找到从给定基类继承的类型集。 It didn't take long to figure out for simple types, but I'm stumped when it comes to generics.很快就弄清楚了简单类型,但是当涉及到泛型时我被难住了。

For this piece of code, the first IsAssignableFrom returns true, but the second returns false.对于这段代码,第一个 IsAssignableFrom 返回 true,但第二个返回 false。 And yet, the final assignment compiles just fine.然而,最终的分配编译得很好。

class class1 { }
class class2 : class1 { }
class generic1<T> { }
class generic2<T> : generic1<T> { }

class Program
{
    static void Main(string[] args)
    {
        Type c1 = typeof(class1);
        Type c2 = typeof(class2);
        Console.WriteLine("c1.IsAssignableFrom(c2): {0}", c1.IsAssignableFrom(c2));

        Type g1 = typeof(generic1<>);
        Type g2 = typeof(generic2<>);
        Console.WriteLine("g1.IsAssignableFrom(g2): {0}", g1.IsAssignableFrom(g2));

        generic1<class1> cc = new generic2<class1>();
    }
}

So how do I determine at run time whether one generic type definition is derived from another?那么如何在运行时确定一个泛型类型定义是否派生自另一个呢?

From the answer to another question :另一个问题的答案

public static bool IsAssignableToGenericType(Type givenType, Type genericType)
{
    var interfaceTypes = givenType.GetInterfaces();

    foreach (var it in interfaceTypes)
    {
        if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType)
            return true;
    }

    if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
        return true;

    Type baseType = givenType.BaseType;
    if (baseType == null) return false;

    return IsAssignableToGenericType(baseType, genericType);
}

The exact code you posted does not return surprising results.您发布的确切代码不会返回令人惊讶的结果。

This says "false":这说“假”:

Type g1 = typeof(generic1<>);
Type g2 = typeof(generic2<>);
Console.WriteLine("g1.IsAssignableFrom(g2): {0}", g1.IsAssignableFrom(g2));

This says "true":这说“真的”:

Type g1 = typeof(generic1<class1>);
Type g2 = typeof(generic2<class1>);
Console.WriteLine("g1.IsAssignableFrom(g2): {0}", g1.IsAssignableFrom(g2));

The difference is that open generic types cannot have instances, so one is not "assignable" to the other.不同之处在于开放的泛型类型不能有实例,因此一个不能“分配”给另一个。

From the docs :文档

Returns true if c and the current Type represent the same type, or if the current Type is in the inheritance hierarchy of c , or if the current Type is an interface that c implements, or if c is a generic type parameter and the current Type represents one of the constraints of c .如果c和当前Type表示相同的类型,或者如果当前Typec的继承层次结构中,或者如果当前Typec实现的接口,或者如果c是泛型类型参数和当前Type ,则返回true表示c的约束之一。 false if none of these conditions are true, or if c is null .如果这些条件都不为真,或者cnull ,则为false

In this case, clearly none of these conditions are true.在这种情况下,显然这些条件都不成立。 And there's an extra note:还有一个额外的说明:

A generic type definition is not assignable from a closed constructed type.泛型类型定义不能从封闭的构造类型分配。 That is, you cannot assign the closed constructed type MyGenericList<int> ( MyGenericList(Of Integer) in Visual Basic) to a variable of type MyGenericList<T> .也就是说,您不能将封闭的构造类型MyGenericList<int> (在 Visual Basic 中为MyGenericList(Of Integer) )分配给MyGenericList<T>类型的变量。

In the following case use the method Konrad Rudolph provided could be wrong, like: IsAssignableToGenericType(typeof(A), typeof(A<>));// return false在以下情况下,使用 Konrad Rudolph 提供的方法可能是错误的,例如: IsAssignableToGenericType(typeof(A), typeof(A<>));// return false

I think here's a better answer我认为这是一个更好的答案

public static bool IsAssignableFrom(Type extendType, Type baseType)
{
    while (!baseType.IsAssignableFrom(extendType))
    {
        if (extendType.Equals(typeof(object)))
        {
            return false;
        }
        if (extendType.IsGenericType && !extendType.IsGenericTypeDefinition)
        {
            extendType = extendType.GetGenericTypeDefinition();
        }
        else
        {
            extendType = extendType.BaseType;
        }
    }
    return true;
}

the test case, see Using IsAssignableFrom with C# generics for detail测试用例,请参阅使用 IsAssignableFrom 和 C# 泛型了解详细信息

using System;

/**
 * Sam Sha - yCoder.com
 *
 * */
namespace Test2
{
    class MainClass
    {
        public static void Main (string[] args)
        {
            string a = "ycoder";
            Console.WriteLine(a is object);
            A aa = new A();
            //Console.WriteLine(aa is A<>);//con't write code like this
            typeof(A<>).IsAssignableFrom(aa.GetType());//return false

            Trace(typeof(object).IsAssignableFrom(typeof(string)));//true
            Trace(typeof(A<>).IsAssignableFrom(typeof(A)));//false

            AAA aaa = new AAA();
            Trace("Use IsTypeOf:");
            Trace(IsTypeOf(aaa, typeof(A<>)));
            Trace(IsTypeOf(aaa, typeof(AA)));
            Trace(IsTypeOf(aaa, typeof(AAA<>)));

            Trace("Use IsAssignableFrom from stackoverflow - not right:");
            Trace(IsAssignableFrom(typeof(A), typeof(A<>))); // error
            Trace(IsAssignableFrom(typeof(AA), typeof(A<>)));
            Trace(IsAssignableFrom(typeof(AAA), typeof(A<>)));

            Trace("Use IsAssignableToGenericType:");
            Trace(IsAssignableToGenericType(typeof(A), typeof(A<>)));
            Trace(IsAssignableToGenericType(typeof(AA), typeof(A<>)));
            Trace(IsAssignableToGenericType(typeof(AAA), typeof(A<>)));
        }

        static void Trace(object log){
                Console.WriteLine(log);
        }

        public static bool IsTypeOf(Object o, Type baseType)
        {
            if (o == null || baseType == null)
            {
                return false;
            }
            bool result = baseType.IsInstanceOfType(o);
            if (result)
            {
                return result;
            }
            return IsAssignableFrom(o.GetType(), baseType);
        }

        public static bool IsAssignableFrom(Type extendType, Type baseType)
        {
            while (!baseType.IsAssignableFrom(extendType))
            {
                if (extendType.Equals(typeof(object)))
                {
                    return false;
                }
                if (extendType.IsGenericType && !extendType.IsGenericTypeDefinition)
                {
                    extendType = extendType.GetGenericTypeDefinition();
                }
                else
                {
                    extendType = extendType.BaseType;
                }
            }
            return true;
        }

        //from stackoverflow - not good enough
        public static bool IsAssignableToGenericType(Type givenType, Type genericType) {
            var interfaceTypes = givenType.GetInterfaces();

            foreach (var it in interfaceTypes)
                if (it.IsGenericType)
                    if (it.GetGenericTypeDefinition() == genericType) return true;

            Type baseType = givenType.BaseType;
            if (baseType == null) return false;

            return baseType.IsGenericType &&
                baseType.GetGenericTypeDefinition() == genericType ||
                IsAssignableToGenericType(baseType, genericType);
        }
    }

    class A{}
    class AA : A{}
    class AAA : AA{}
}

I have a different Approach that resolves this issue, Here are my classes我有一种不同的方法可以解决这个问题,这是我的课程

public class Signal<T>{
   protected string Id {get; set;} //This must be here, I use a property because MemberInfo is returned in an array via GetMember() reflection function
   //Some Data and Logic And stuff that involves T
}

public class OnClick : Signal<string>{}

Now if I have an instance of type OnClick but I dont know that, and I want to find out if I have an instance of anything which inherits from Signal<> of any type?现在,如果我有一个 OnClick 类型的实例,但我不知道,我想知道我是否有一个从任何类型的 Signal<> 继承的任何实例? I do this我这样做

Type type = GetTypeWhomISuspectMightBeAGenericSignal();

PropertyInfo secretProperty = type.GetProperty("Id", BindingFlags.NonPublic | BindingFlags.Instance);

Type SpecificGenericType = secretProperty.DeclaringType; //This is the trick

bool IsMyTypeInheriting = SpecificGenericType.IsGenericType && SpecificGenericType.GetGenericTypeDefinition() == typeof(Signal<>); //This way we are getting the genericTypeDefinition and comparing it to any other genericTypeDefinition of the same argument length.

So this works for me, its not recursive, and it uses a trick via a designated property.所以这对我有用,它不是递归的,它通过指定的属性使用了一个技巧。 It has limitations that its hard to write a function that checks assignability for all generics ever.它的局限性在于很难编写一个检查所有泛型的可分配性的函数。 But for a specific type it works但对于特定类型,它可以工作

Obviously you need to check if() conditions better and stuff, but these are the Raw lines required to evaluate assignability of a type to its base generic, this way.显然,您需要更好地检查 if() 条件和东西,但这些是通过这种方式评估类型对其基本泛型的可分配性所需的原始行。

Hope this helps希望这可以帮助

My two cents.我的两分钱。 IMHO it doesn't make much sense to separate implements, derives or the original functionality of IsAssignableFrom,恕我直言,分离 IsAssignableFrom 的实现、派生或原始功能没有多大意义,

Constructing from the answers previously given, this is how I do it:根据之前给出的答案构建,这就是我的做法:

public static bool ImplementsOrDerives(this Type @this, Type from)
{
    if(from is null)
    {
        return false;
    }
    else if(!from.IsGenericType)
    {
        return from.IsAssignableFrom(@this);
    }
    else if(!from.IsGenericTypeDefinition)
    {
        return from.IsAssignableFrom(@this);
    }
    else if(from.IsInterface)
    {
        foreach(Type @interface in @this.GetInterfaces())
        {
            if(@interface.IsGenericType && @interface.GetGenericTypeDefinition() == from)
            {
                return true;
            }
        }
    }

    if(@this.IsGenericType && @this.GetGenericTypeDefinition() == from)
    {
        return true;
    }

    return @this.BaseType?.ImplementsOrDerives(from) ?? false;
}

You need to compare the contained type.您需要比较包含的类型。 See: How to get the type of T from a member of a generic class or method?请参阅: 如何从泛型类或方法的成员中获取 T 的类型?

In other words, I think you need to check whether the type being contained by the generic class is assignable rather than the generic class itself.换句话说,我认为您需要检查泛型类包含的类型是否是可分配的,而不是泛型类本身。

@konrad_ruldolph's answer is mostly correct, but it requires you to know the base type/interface is an open generic. @konrad_ruldolph 的回答大部分是正确的,但它要求您知道基本类型/接口是一个开放的泛型。 I propose an improvement that combines a non-generic test with a loop to test for generic match.我提出了一种改进,将非泛型测试与循环相结合来测试泛型匹配。

    public static class Ext
    {
        public static bool IsAssignableToGeneric(
            this Type assignableFrom,
            Type assignableTo)
        {
            bool IsType(Type comparand)
                => assignableTo.IsAssignableFrom(comparand)
                    || (comparand.IsGenericType
                    && comparand.GetGenericTypeDefinition() == assignableTo);

            while (assignableFrom != null)
            {
                if (IsType(assignableFrom)
                    || assignableFrom
                    .GetInterfaces()
                    .Any(IsType))
                {
                    return true;
                }

                assignableFrom = assignableFrom.BaseType;
            }

            return false;
        }
    }

创建扩展方法并使用链接您可以这样做:

public static bool IsAssignableFromGenericInterface(this Type type, Type genericInterface) => type.GetInterfaces().Any(@interface => @interface.IsAssignableFrom(genericInterface));

I also would like to share my code with you.我也想和你分享我的代码。 Here the generic arguments are checked for any compatibility and is working with interfaces.在这里检查通用参数的兼容性并使用接口。

public static bool IsAssignableToGeneric(this Type sourceType, Type targetType)
{
    bool IsAssignable(Type comperand)
    {
        if (comperand.IsAssignableTo(targetType))
            return true;

        if (comperand.IsGenericType && targetType.IsGenericType && comperand.GetGenericTypeDefinition() == targetType.GetGenericTypeDefinition())
        {
            for (int i = 0; i < targetType.GenericTypeArguments.Length; i++)
            {
                Type comperandArgument = comperand.GenericTypeArguments[i];
                Type targetArgument = targetType.GenericTypeArguments[i];

                // suggestion for improvement: forward the type check recursivley also here
                if (!comperandArgument.IsGenericTypeParameter && !targetArgument.IsGenericTypeParameter && !comperandArgument.IsAssignableTo(targetArgument))
                    return false;
            }

            return true;
        }

        return false;
    }

    if (IsAssignable(sourceType))
        return true;

    if (targetType.IsInterface && sourceType.GetInterfaces().Any(IsAssignable))
        return true;

    return false;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM