Decide if class has generic ancestor of specific generic argument type

前提是你 提交于 2021-01-07 02:31:04

问题


Say I have a class CoolStorageClass, which inherits from StorageClassBase:

public abstract class StorageClassBase
{
}

public class CoolStorageClass : StorageClassBase
{
}

Then I have a generic abstract BaseClass<T>. It is important, that T can only be of type StorageClassBase.

public abstract class BaseClass<T> where T : StorageClassBase
{
}

Then I have the implementation of the BaseClass with T as CoolStorageClass in the form of CoolClass:

public class CoolClass : BaseClass<CoolStorageClass>
{
}

I want to select all of my object, which are implementing the BaseClass<StorageClassBase> abstract class.

  1. does it make sense to check the generic of BaseClass? I mean, I could have classes, which inherit from BaseClass<DifferentStorageClassBase>... I ask this, because the linked answer below does not care about the generic parameter of the generic type, only the type itself.

  2. how do I check if a Type implements BaseClass<StorageClassBase>? I have found following answer, but it does not check the type of the generic parameter. So I modified it into this:

    public static class TypeExtensions
    {
        //https://stackoverflow.com/a/457708
        public static bool HasBaseClassOf(this Type t, Type toCheck, Type genericParameter)
        {
            while ((t != null) && (t != typeof(object)))
            {
                var cur = t.IsGenericType ? t.GetGenericTypeDefinition() : t;
                if (toCheck == cur)
                {
                    //also check whether the generic types match
                    if (t.GenericTypeArguments[0].IsSubclassOf(genericParameter))
                    {
                        return true;
                    }
                }
                t = t.BaseType;
            }
    
            return false;
        }
    }
    

But this only checks for one generic type, and I don't understand why I have to check t.GenericTypeArguments instead of cur.GenericTypeArguments.

  1. What is the correct way to check for all the generic type arguments and the BaseClass?

  2. Currently I have to call the function like this: o.GetType().HasBaseClassOf(typeof(BaseClass<StorageClassBase>), typeof(StorageClassBase)). How should I modify the function to be able to call it like this: o.GetType().HasBaseClassOf(typeof(BaseClass<StorageClassBase>))?


Minimal reproducible example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MinimalReproducibleExample
{
public abstract class StorageClassBase
{
    //does something
}

public class CoolStorageClass : StorageClassBase
{
}

public abstract class BaseClass<T> where T : StorageClassBase
{
}
public class CoolClass : BaseClass<CoolStorageClass>
{
}

public static class TypeExtensions
{
    //https://stackoverflow.com/a/457708
    public static bool HasBaseClassOf(this Type t, Type toCheck, Type genericParameter)
    {
        while ((t != null) && (t != typeof(object)))
        {
            var cur = t.IsGenericType ? t.GetGenericTypeDefinition() : t;
            if (toCheck == cur)
            {
                //also check whether the generic types match
                if (t.GenericTypeArguments[0].IsSubclassOf(genericParameter))
                {
                    return true;
                }
            }
            t = t.BaseType;
        }

        return false;
    }
}

class Program
{
    static void Main(string[] args)
    {
        List<object> myObjects = new List<object>();
        myObjects.Add(new CoolClass());
        myObjects.Add(new CoolClass());
        myObjects.Add(new object());
        myObjects.Add(new object());

        var t1 = myObjects.Where(o => o.GetType().HasBaseClassOf(typeof(BaseClass<>), typeof(StorageClassBase))).ToList();


        Console.ReadKey();
    }
}
}

回答1:


Not sure if this is clear to you or not, but GetGenericTypeDefinition returns a so called open generic type, i.e. baseClass<>. This can be useful if you do not care about the generic arguments for a type. But that does not seem to do quite what you want to.

To simplify the problem statement a bit, lets declare a bunch of shorter types that we can use, simply so we do not have to write BaseClass so many times.

public class T1{}
public class T2 : T1{}
public class Tx : T1{}

public class U1<T> where T : T1{}
public class U2 : U1<T2>{}
public class U2a : U2{}
public class Ux : U1<T1>{}

If I understand your requirements correctly you want to check if a object inherit from a specificed open generic type, and if the type argument is assignable from some other type.

I.e. the following statements should hold:

    [Test]
    public void Test()
    {
        Assert.IsTrue(Test<U2>(new U2a()));
        Assert.IsTrue(Test<U1<T1>>(new U2()));
        Assert.IsTrue(Test<U1<T2>>(new U2()));
        Assert.IsTrue(Test<U1<T2>>(new U2a()));
        
        Assert.IsFalse(Test<U1<Tx>>(new U2a()));
        Assert.IsFalse(Test<Ux>(new U2a()));
    }

The following test should fulfill these statements:

    public bool Test<T>(object obj)
    {
        var tType = typeof(T);
        if (tType.IsGenericType)
        {
            var genericType = tType.GetGenericTypeDefinition();
            var genericArguments = tType.GenericTypeArguments;
            return obj.GetType().BaseTypes().Any(IsMatchingGenericType);
            bool IsMatchingGenericType(Type t)
            {
                if (!t.IsGenericType)
                    return false;
                if (t.GetGenericTypeDefinition() != genericType)
                    return false;
                if (t.GenericTypeArguments.Length != genericArguments.Length)
                {
                    return false;
                }

                for (int i = 0; i < genericArguments.Length; i++)
                {
                    if (!genericArguments[i].IsAssignableFrom(t.GenericTypeArguments[i]))
                    {
                        return false;
                    }
                }
                return true;
            }
        }
        else
        {
            return tType.IsInstanceOfType(obj);
        }
    }

This uses IsAssignableFrom rather than IsSubclassOf of, there is some differences between the two if you are using interfaces.

Note that the following does not compile U1<T1> t1 = new U1<T2>(), since the generic type is not covariant. For this you would need to declare a covariant interface, and if you do that, you can just is IsAssignableFrom instead of that large cumbersome method.

I also wonder if you are approaching this in the correct way. It is usually better to put common functionality in separate classes rather than using inheritance. In most cases you want to avoid checking types, and let put the logic in the class instead. If you want to make the logic type-dependent but still separate from the classes, the visitor pattern can often be used.



来源:https://stackoverflow.com/questions/65318411/decide-if-class-has-generic-ancestor-of-specific-generic-argument-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!