0

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();
    }
}
}
G. B.
  • 528
  • 2
  • 15
  • 1
    There is a lot to unpack here. Lets take a step back, what do you need to do, check an instance or a type, or a generic type? – TheGeneral Dec 16 '20 at 07:13
  • I need to find all my objects of type inherited from a specific BaseClass and have storage classes which inherit from a specific StorageClassBase. – G. B. Dec 16 '20 at 07:23
  • Say I have some classes, which do something (data acquisition in this case). This something is very similar for all the classes, therefore I extracted the common functionality in the abstract BaseClass. But, every class stores the data in a different storage class, which also have a common functionality, extracted to StorageClassBase. But there are classes, that are very different, and do not implement this interface. I have all the classes in a list, and need to select and use only those, which have this specific interface with the specific storage class. – G. B. Dec 16 '20 at 07:32

1 Answers1

0

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.

JonasH
  • 28,608
  • 2
  • 10
  • 23