Check if object is of non-specific generic type in C#

巧了我就是萌 提交于 2020-08-18 05:35:09

问题


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.


回答1:


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




回答2:


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



回答3:


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.




回答4:


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.




回答5:


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



来源:https://stackoverflow.com/questions/18687264/check-if-object-is-of-non-specific-generic-type-in-c-sharp

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