Проверьте, не является ли объект неспецифическим родовым типом в С#

Скажем, у меня есть следующий класс:

public class General<T> { }

И я хочу узнать, имеет ли объект этот тип. Я знаю, что могу использовать отражение, чтобы выяснить, имеет ли объект этот общий тип с Type.GetGenericTypeDefinition, но я хочу этого избежать.

Можно ли сделать что-то вроде obj is General<T> или obj.GetType().IsAssignableFrom(typeof(General<T>))?

Я очень удивлен, что я не мог найти подобный вопрос, хотя, возможно, я использовал неправильные ключевые слова в моих поисках.

Ответ 1

Вы можете сделать это:

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<>));

Где GetBaseTypes - следующий метод расширения:

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()));
}

кредиты Ответы на слаксы

Ответ 2

Есть много ответов на похожие questions, но все они требуют отражения, чтобы подойти к иерархии типов. Я подозреваю, что лучшего способа нет. Если производительность критическая, кеширование результата может быть опцией. Вот пример использования ConcurrentDictionary в качестве простого кеша. Затем стоимость сводится к простому поиску типа (через GetType) и поиску ConcurrentDictionary после инициализации кэша.

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;
}

И вы будете использовать его следующим образом:

class I : General<int> { }

object o = new I();
Console.WriteLine(o is General<int>); // true
Console.WriteLine(o.GetType().IsSubclassOfRawGeneric(typeof(General<>))); //true

Ответ 3

Определения общего типа, которые создаются с помощью параметров типа, не имеют никакого отношения к другим экземплярам универсального типа. Они также не имеют никакого отношения к определению общего типа. Они полностью несовместимы, когда речь идет о назначении и времени выполнения. Если бы они не были, можно было бы разбить систему типов.

По этой причине выполнение выполнения не поможет. Вам придётся прибегнуть к Type.GetGenericTypeDefinition. Вы можете абстрагировать это на вспомогательную функцию и сохранить свой код относительно чистым таким образом.

Ответ 4

Если общий класс или интерфейс имеют члены, которые могут использоваться кодом, который содержал ссылку в более общей форме типа Object, но не имел фактического общего типа, такие члены должны быть открыты в не- общий базовый класс или интерфейс. Рамочная система во многих случаях не соблюдает этот принцип, но нет причин, по которым следует следовать их примеру. Например, такой тип, как IList<T>, мог бы быть получен из IListBase, который включал или унаследовал таких членов, как:

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;}

Ни один из этих участников никоим образом не будет полагаться на тип элементов, хранящихся в списке, и можно было бы написать методы, чтобы делать такие вещи, как сортировать список (если его Features указал, что он доступен для записи и умеет сравнивать элементы), не зная ничего о типе элемента. Если общий интерфейс наследуется от не-общего интерфейса, код, требующий не общих функций, может просто быть перенесен в не общий тип интерфейса и использовать его напрямую.