Как определить, является ли тип другим типичным типом

Пример:

public static void DoSomething<K,V>(IDictionary<K,V> items) {
   items.Keys.Each(key => {
      if (items[key] **is IEnumerable<?>**) { /* do something */ }
      else { /* do something else */ }
}

Можно ли это сделать без использования рефлексии? Как сказать IEnumerable в С#? Должен ли я просто использовать IEnumerable, поскольку IEnumerable < > реализует IEnumerable?

Ответ 1

Ранее принятый ответ хорош, но это неправильно. К счастью, ошибка небольшая. Проверка на IEnumerable недостаточно, если вы действительно хотите узнать об общей версии интерфейса; существует много классов, которые реализуют только неонный интерфейс. Я дам ответ через минуту. Во-первых, я хотел бы отметить, что принятый ответ слишком сложный, так как следующий код достигнет того же в данных обстоятельствах:

if (items[key] is IEnumerable)

Это делает еще больше, потому что он работает для каждого элемента отдельно (а не для общего подкласса V).

Теперь, для правильного решения. Это немного сложнее, потому что мы должны взять общий тип IEnumerable`1 (т.е. Тип IEnumerable<> с одним параметром типа) и ввести правильный общий аргумент:

static bool IsGenericEnumerable(Type t) {
    var genArgs = t.GetGenericArguments();
    if (genArgs.Length == 1 &&
            typeof(IEnumerable<>).MakeGenericType(genArgs).IsAssignableFrom(t))
        return true;
    else
        return t.BaseType != null && IsGenericEnumerable(t.BaseType);
}

Вы можете легко проверить правильность этого кода:

var xs = new List<string>();
var ys = new System.Collections.ArrayList();
Console.WriteLine(IsGenericEnumerable(xs.GetType()));
Console.WriteLine(IsGenericEnumerable(ys.GetType()));

дает:

True
False

Не следует чрезмерно беспокоиться о том, что это использует отражение. Хотя верно, что это добавляет служебные данные во время выполнения, также используется оператор is.

Конечно, вышеуказанный код ужасно ограничен и может быть расширен в более общедоступный метод IsAssignableToGenericType. Следующая реализация несколько неверна 1 и я оставил ее здесь только в исторических целях. Не используйте его. Вместо этого Джеймс обеспечил отличную, правильную реализацию в своем ответе.

public static bool IsAssignableToGenericType(Type givenType, Type genericType) {
    var interfaceTypes = givenType.GetInterfaces();

    foreach (var it in interfaceTypes)
        if (it.IsGenericType)
            if (it.GetGenericTypeDefinition() == genericType) return true;

    Type baseType = givenType.BaseType;
    if (baseType == null) return false;

    return baseType.IsGenericType &&
        baseType.GetGenericTypeDefinition() == genericType ||
        IsAssignableToGenericType(baseType, genericType);
}

1 Он терпит неудачу, когда genericType совпадает с givenType; по той же причине он терпит неудачу для типов с нулевым значением, т.е.

IsAssignableToGenericType(typeof(List<int>), typeof(List<>)) == false
IsAssignableToGenericType(typeof(int?), typeof(Nullable<>)) == false

Ive создал gist с полным набором тестовых примеров.

Ответ 2

Большое спасибо за этот пост. Я хотел предоставить версию решения Konrad Rudolph, которое лучше меня работало. У меня были незначительные проблемы с этой версией, особенно при тестировании, если тип - тип значения с нулевым значением:

public static bool IsAssignableToGenericType(Type givenType, Type genericType)
{
    var interfaceTypes = givenType.GetInterfaces();

    foreach (var it in interfaceTypes)
    {
        if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType)
            return true;
    }

    if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
        return true;

    Type baseType = givenType.BaseType;
    if (baseType == null) return false;

    return IsAssignableToGenericType(baseType, genericType);
}

Ответ 3

Слово предупреждения об общих типах и использование IsAssignableFrom()...

Скажите, что у вас есть следующее:

public class MyListBase<T> : IEnumerable<T> where T : ItemBase
{
}

public class MyItem : ItemBase
{
}

public class MyDerivedList : MyListBase<MyItem>
{
}

Вызов IsAssignableFrom из базового типа списка или из производного типа списка возвращает false, но явно MyDerivedList наследует MyListBase<T>. (Быстрое примечание для Джеффа, дженерики абсолютно должны быть завернуты в кодовый блок или тильды, чтобы получить <T>, в противном случае он опустится. Это предназначено?) Проблема связана с тем, что MyListBase<MyItem> рассматривается как совершенно другое типа MyListBase<T>. Следующая статья может объяснить это немного лучше. http://mikehadlow.blogspot.com/2006/08/reflecting-generics.html

Вместо этого попробуйте следующую рекурсивную функцию:

    public static bool IsDerivedFromGenericType(Type givenType, Type genericType)
    {
        Type baseType = givenType.BaseType;
        if (baseType == null) return false;
        if (baseType.IsGenericType)
        {
            if (baseType.GetGenericTypeDefinition() == genericType) return true;
        }
        return IsDerivedFromGenericType(baseType, genericType);
    }

/EDIT: новое сообщение Konrad, которое учитывает общую рекурсию, а также интерфейсы. Очень приятная работа.:)

/EDIT2: Если проверяется, является ли genericType интерфейсом, могут быть реализованы преимущества производительности. Проверка может быть блоком if вокруг текущего кода интерфейса, но если вы заинтересованы в использовании .NET 3.5, мой друг предлагает следующее:

    public static bool IsAssignableToGenericType(Type givenType, Type genericType)
    {
        var interfaces = givenType.GetInterfaces().Where(it => it.IsGenericType).Select(it => it.GetGenericTypeDefinition());
        var foundInterface = interfaces.FirstOrDefault(it => it == genericType);
        if (foundInterface != null) return true;

        Type baseType = givenType.BaseType;
        if (baseType == null) return false;

        return baseType.IsGenericType ?
            baseType.GetGenericTypeDefinition() == genericType :
            IsAssignableToGenericType(baseType, genericType);
    }

Ответ 4

if (typeof(IEnumerable).IsAssignableFrom(typeof(V))) {

Ответ 5

Спасибо за отличную информацию. Для уверенности я переработал это в метод расширения и сводил его к одному утверждению.

public static bool IsAssignableToGenericType(this Type givenType, Type genericType)
{
  return givenType.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == genericType) ||
         givenType.BaseType != null && (givenType.BaseType.IsGenericType && givenType.BaseType.GetGenericTypeDefinition() == genericType ||
                                        givenType.BaseType.IsAssignableToGenericType(genericType));
}

Теперь его можно легко вызвать с помощью

sometype.IsAssignableToGenericType(TypeOf (MyGenericType < > ))

Ответ 6

Я бы использовал перегрузку:

public static void DoSomething<K,V>(IDictionary<K,V> items)
  where V : IEnumerable
{
   items.Keys.Each(key => { /* do something */ });
}

public static void DoSomething<K,V>(IDictionary<K,V> items)
{
   items.Keys.Each(key => { /* do something else */ });
}

Ответ 7

Я не уверен, что понимаю, что вы имеете в виду здесь. Вы хотите узнать, имеет ли объект любой общий тип или вы хотите проверить, является ли он конкретным родовым типом? Или вы просто хотите знать, является ли перечислимым?

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

Кроме того, вы не можете использовать оператор 'is' для типов.

// Not allowed
if (string is Object)
  Foo();
// You have to use 
if (typeof(object).IsAssignableFrom(typeof(string))
  Foo();

Подробнее см. этот вопрос о типах. Возможно, это поможет вам.

Ответ 9

Раньше я думал, что такая ситуация может быть разрешима аналогично @Thomas Danecker , но добавляет еще один аргумент шаблона:

public static void DoSomething<K, V, U>(IDictionary<K,V> items)
    where V : IEnumerable<U> { /* do something */ }
public static void DoSomething<K, V>(IDictionary<K,V> items)
                             { /* do something else */ }

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

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