14

Say I have the following class:

public class General<T> { }

And I want to find out if an object is of that type. I know I can use reflection to find out whether the object is of that generic type with Type.GetGenericTypeDefinition, but I want to avoid that.

Is it possible to do something like obj is General<T>, or obj.GetType().IsAssignableFrom(typeof(General<T>))?

I'm quite surprised that I couldn't find a similar question, although I may have used wrong keywords in my searches.

svick
  • 236,525
  • 50
  • 385
  • 514
EZLearner
  • 1,614
  • 16
  • 25
  • 1
    Out of curiosity, if you have the answer, what do you plan to do with it? There isn't a whole lot you can do without reflection, and your question asks to avoid reflection. –  Sep 08 '13 at 18:45
  • I'm trying to avoid useless reflection, though its run-time performance is baaaaaad (Like a sheep :)) – EZLearner Sep 08 '13 at 18:46
  • @EZSlaver To satiate _my_ curiosity, do you have a scenario to demonstrate where this is a problem? The argument for _slowness_ of reflection has gone before, but I feel it's been stigmatised to the point of superstitious avoidance in any solution. – Grant Thomas Sep 08 '13 at 18:51
  • Your class is not `sealed`. What if `obj` is of run-time type `X`, and `X` has base class `General`? – Jeppe Stig Nielsen Sep 08 '13 at 18:51
  • @JeppeStigNielsen, please rephrase you question. – EZLearner Sep 08 '13 at 18:52
  • I'm thinking that depending on what you're really looking for, you may be better off with overloaded functions `f(object o)` and `f(General o)`, and then simply call `f((dynamic)o)` (which uses reflection behind the scenes, but isolates it to a single point, after which you may not need reflection at all). –  Sep 08 '13 at 18:52
  • (I already rephrased, sorry.) – Jeppe Stig Nielsen Sep 08 '13 at 18:52
  • @GrantThomas , I am using it inside a function that's called many time due to proprietary serialization. Changing that function to use reflection may heavily alter the performance of the entire system. – EZLearner Sep 08 '13 at 18:54
  • @EZSlaver Am I right in inferring that you haven't tested, then? – Grant Thomas Sep 08 '13 at 18:55
  • @JeppeStigNielsen, that's a use-case to consider. That's why I've asked that question. – EZLearner Sep 08 '13 at 18:57
  • @GrantThomas You infer correctly. I'm trying to avoid that, though it's a lot of headache for the QA, and I do not want to add the requirement of comprehensive regression checks to the feature. Again, that's why I ask. – EZLearner Sep 08 '13 at 19:01

5 Answers5

7

You can do this:

var obj = new General<int>();
var type = obj.GetType();
var isGeneral = 
(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(General<>)) ||
type.GetBaseTypes().Any(x => x.IsGenericType && 
                             x.GetGenericTypeDefinition() == typeof(General<>));

Where GetBaseTypes is the following extension method:

public static IEnumerable<Type> GetBaseTypes(this Type type)
{
    if (type.BaseType == null) return type.GetInterfaces();

    return new []{type}.Concat(
           Enumerable.Repeat(type.BaseType, 1)
                     .Concat(type.GetInterfaces())
                     .Concat(type.GetInterfaces().SelectMany<Type, Type>(GetBaseTypes))
                     .Concat(type.BaseType.GetBaseTypes()));
}

credits to Slacks answer

Community
  • 1
  • 1
digEmAll
  • 56,430
  • 9
  • 115
  • 140
  • 2
    `GetGenericTypeDefinition()` throws exceptions for non-generic types. The way I'm reading the question, you should be able to call it for `int` (and get `false`), and you should be able to call it for `class I : General` (and get `true`). –  Sep 08 '13 at 18:43
  • Good to know. Although I think that `IsAssignableFrom` makes no sense for open generic types anyway because they can never be variable types and are therefore never assigned to each other. – usr Sep 08 '13 at 18:46
  • 2
    That check avoids the exception for my `class I`, but gives the wrong result. `new I() is General` would return `true`, but your check would not. –  Sep 08 '13 at 18:47
  • Could you avoid requiring `Type.GetGenericTypeDefinition`? – EZLearner Sep 08 '13 at 18:50
  • 1
    It's almost right: `type.GetBaseTypes()` does not include `type` itself, so this returns false for the test in your answer (`new General`). –  Sep 08 '13 at 19:16
  • What would be the need for `type.GetInterfaces().SelectMany(GetBaseTypes)` ? Wouldnt `type.GetInterfaces` return all the interfaces, even from base? – nawfal Aug 03 '20 at 02:33
1

There are many answers to similar questions, but they all require reflection to walk up the type hierarchy. I suspect there is no better way. If performance is critical, caching the result maybe an option. Here is an example using a ConcurrentDictionary as a simple cache. Then the cost is reduced to a simple type lookup (via GetType) and a ConcurrentDictionary lookup after the cache has been initialized.

using System.Collections.Concurrent;

private static ConcurrentDictionary<Tuple<Type,Type>, bool> cache = new ConcurrentDictionary<Tuple<Type,Type>, bool>();

public static bool IsSubclassOfRawGeneric(this Type toCheck, Type generic) {
    var input = Tuple.Create(toCheck, generic);
    bool isSubclass = cache.GetOrAdd(input, key => IsSubclassOfRawGenericInternal(toCheck, generic));
    return isSubclass;
}

private static bool IsSubclassOfRawGenericInternal(Type toCheck, Type generic) {
    while (toCheck != null && toCheck != typeof(object)) {
        var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
        if (generic == cur) {
            return true;
        }
        toCheck = toCheck.BaseType;
    }
    return false;
}

And you would use it like this:

class I : General<int> { }

object o = new I();
Console.WriteLine(o is General<int>); // true
Console.WriteLine(o.GetType().IsSubclassOfRawGeneric(typeof(General<>))); //true
Community
  • 1
  • 1
Mike Zboray
  • 39,828
  • 3
  • 90
  • 122
0

Generic type definitions that are instantiated with type parameters have no relation at all to other generic type instantiations. They also have no relation to the generic type definition. They are completely incompatible when it comes to assignment and runtime casting. If they weren't it would be possible to break the type system.

For that reason runtime casts will not help. You will indeed have to resort to Type.GetGenericTypeDefinition. You can abstract that into a helper function and keep your code relatively clean that way.

usr
  • 168,620
  • 35
  • 240
  • 369
  • 1
    This is correct in most cases, of course. I found an example `X` that seems to be a little different, namely if both (1) the type `X<>` is co- or contravariant in the type parameter `T` in question, and (2) the same type parameter `T` is constrained to a specific base class `B`. In that case `typeof(X).IsAssignableFrom(typeof(X<>))` (for covariant `X<>`) or `typeof(X<>).IsAssignableFrom(typeof(X))` (for contravariance). I am not sure if this is "unintended". Of course `X` must be an interface or delegate type in order to be co- or contravariant. – Jeppe Stig Nielsen Sep 08 '13 at 19:51
0

If a generic class or interface has members which could be used by code which held a reference in a more general form like Object but didn't have the actual generic type available, such members should be exposed in a non-generic base class or interface. The Framework has in many cases failed to abide by that principle, but there's no reason one must follow their example. For example, a type like IList<T> could have derived from IListBase which included or inherited members like:

int Count {get;}
void Delete(int index);
void Clear();
void Swap(int index1, int index2);
int Compare(int index1, int index2);
// Return an object with a `StoreToIndex(int)` method
// which would store it to the list it came from.
ListItemHolder GetItemHolder(int index);
ListFeatures Features {get;}

None of those members would rely in any way upon the type of items held within the list, and one could write methods to do things like sort a list (if its Features indicated that it was writable and knew how to compare items) without having to know anything about the element type. If a generic interface inherits from a non-generic interface, code needing the non-generic functions could simply cast to the non-generic interface type and use it directly.

supercat
  • 77,689
  • 9
  • 166
  • 211
0

For a more generalized solution, that works with any parent type (base class as well as interfaces):

    public static bool IsCompatibleWith(this Type type, Type parentType)
    {
        if (type == null)
        {
            throw new ArgumentNullException(nameof(type));
        }

        if (parentType.IsAssignableFrom(type))
        {
            return true;
        }

        return type.GetAssignableTypes()
           .Where(t => t.IsGenericType)
           .Any(t=> t.GetGenericTypeDefinition() == parentType);
    }

    /// <summary>
    /// Gets all parent types including the currrent type.
    /// </summary>
    public static IEnumerable<Type> GetAssignableTypes(this Type type)
    {
        if (type == null)
        {
            throw new ArgumentNullException(nameof(type));
        }

        // First check for interfaces because interface types don't have base classes.
        foreach (Type iType in type.GetInterfaces())
        {
            yield return iType;
        }

        // Then check for base classes.
        do
        {
            yield return type;
            type = type.BaseType;
        }
        while (type != null);
    }

Come up with better method names. Perhaps calling it IsCompatibleWith is misleading. Maybe IsKindOf ? Also, GetAssignableTypes can also be called GetParentTypes but that is also misleading. Naming is hard. Documenting it is better.


Some tests:

IsCompatibleWith(typeof(List<int>), typeof(IList<int>))
true

IsCompatibleWith(typeof(List<>), typeof(IList<>))
true

IsCompatibleWith(typeof(List<int>), typeof(IList<>))
true

IsCompatibleWith(typeof(List<int>), typeof(IList<string>))
false

nawfal
  • 70,104
  • 56
  • 326
  • 368