Тестирование, если объект имеет универсальный тип в C #

134

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

public bool Test()
{
    List<int> list = new List<int>();
    return list.GetType() == typeof(List<>);
}

Что я делаю не так и как мне выполнить этот тест?

Richbits
источник

Ответы:

201

Если вы хотите проверить, является ли это экземпляром универсального типа:

return list.GetType().IsGenericType;

Если вы хотите проверить, является ли это универсальным List<T>:

return list.GetType().GetGenericTypeDefinition() == typeof(List<>);

Как указывает Джон, это проверяет точную эквивалентность типов. Возвращение falseне обязательно означает list is List<T>возврат false(т. Е. Объект не может быть присвоен List<T>переменной).

Мехрдад Афшари
источник
9
Это не обнаружит подтипы, хотя. Смотри мой ответ. Это также намного сложнее для интерфейсов :(
Джон Скит
1
Вызов GetGenericTypeDefinition сгенерирует, если это не универсальный тип. Убедитесь, что вы проверите это в первую очередь.
Килхоффер
85

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

К сожалению, не все так просто. Это не так уж плохо, если универсальный тип является классом (как в данном случае), но это сложнее для интерфейсов. Вот код для класса:

using System;
using System.Collections.Generic;
using System.Reflection;

class Test
{
    static bool IsInstanceOfGenericType(Type genericType, object instance)
    {
        Type type = instance.GetType();
        while (type != null)
        {
            if (type.IsGenericType &&
                type.GetGenericTypeDefinition() == genericType)
            {
                return true;
            }
            type = type.BaseType;
        }
        return false;
    }

    static void Main(string[] args)
    {
        // True
        Console.WriteLine(IsInstanceOfGenericType(typeof(List<>),
                                                  new List<string>()));
        // False
        Console.WriteLine(IsInstanceOfGenericType(typeof(List<>),
                                                  new string[0]));
        // True
        Console.WriteLine(IsInstanceOfGenericType(typeof(List<>),
                                                  new SubList()));
        // True
        Console.WriteLine(IsInstanceOfGenericType(typeof(List<>),
                                                  new SubList<int>()));
    }

    class SubList : List<string>
    {
    }

    class SubList<T> : List<T>
    {
    }
}

РЕДАКТИРОВАТЬ: Как отмечено в комментариях, это может работать для интерфейсов:

foreach (var i in type.GetInterfaces())
{
    if (i.IsGenericType && i.GetGenericTypeDefinition() == genericType)
    {
        return true;
    }
}

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

Джон Скит
источник
2
Просто обнаружил проблему с этим. Это только идет вниз по одной линии наследования. Если, по пути, у вас есть база с базовым классом и интерфейсом, который вы ищете, это идет только по пути классов.
Groxx
1
@Groxx: правда. Я только что заметил, что упоминаю об этом в ответе: «Это не так уж плохо, если универсальный тип является классом (как в данном случае), но это сложнее для интерфейсов. Вот код для класса»
Джон Скит
1
Что если у вас нет способа узнать <T>? Мол, это может быть int или string, но вы этого не знаете. Казалось бы, это порождает ложные негативы ... поэтому у вас нет T для использования, вы просто просматриваете свойства какого-либо объекта, а один представляет собой список. Откуда вы знаете, что это список, чтобы вы могли его распечатать? Под этим я подразумеваю, что у вас нет ни T, ни типа для использования. Вы можете угадать каждый тип (это List <int>? Это List <string>?), Но что вы хотите знать, это ЭТО СПИСОК AA? На этот вопрос сложно ответить.
@RiverC: Да, вы правы - это довольно трудно ответить, по разным причинам. Если вы говорите только о классе, это не так уж плохо ... вы можете продолжать подниматься по дереву наследования и посмотреть, попадете ли вы List<T>в ту или иную форму. Если вы включите интерфейсы, это действительно сложно.
Джон Скит
3
Вы не могли бы заменить цикл IsInstanceOfGenericTypeвызовом IsAssignableFromвместо оператора равенства ( ==)?
slawekwin
7

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

public static class Extension
{
    public static bool IsGenericList(this object o)
    {
       return IsGeneric((dynamic)o);
    }

    public static bool IsGeneric<T>(List<T> o)
    {
       return true;
    }

    public static bool IsGeneric( object o)
    {
        return false;
    }
}



var l = new List<int>();
l.IsGenericList().Should().BeTrue();

var o = new object();
o.IsGenericList().Should().BeFalse();
Дэвид Десмезон
источник
7

Это два моих любимых метода расширения, которые охватывают большинство крайних случаев проверки универсального типа:

Работает с:

  • Несколько (универсальных) интерфейсов
  • Несколько (общих) базовых классов
  • Имеет перегрузку, которая будет «выходить» из определенного универсального типа, если он возвращает true (см. Пример для модульного теста):

    public static bool IsOfGenericType(this Type typeToCheck, Type genericType)
    {
        Type concreteType;
        return typeToCheck.IsOfGenericType(genericType, out concreteType); 
    }
    
    public static bool IsOfGenericType(this Type typeToCheck, Type genericType, out Type concreteGenericType)
    {
        while (true)
        {
            concreteGenericType = null;
    
            if (genericType == null)
                throw new ArgumentNullException(nameof(genericType));
    
            if (!genericType.IsGenericTypeDefinition)
                throw new ArgumentException("The definition needs to be a GenericTypeDefinition", nameof(genericType));
    
            if (typeToCheck == null || typeToCheck == typeof(object))
                return false;
    
            if (typeToCheck == genericType)
            {
                concreteGenericType = typeToCheck;
                return true;
            }
    
            if ((typeToCheck.IsGenericType ? typeToCheck.GetGenericTypeDefinition() : typeToCheck) == genericType)
            {
                concreteGenericType = typeToCheck;
                return true;
            }
    
            if (genericType.IsInterface)
                foreach (var i in typeToCheck.GetInterfaces())
                    if (i.IsOfGenericType(genericType, out concreteGenericType))
                        return true;
    
            typeToCheck = typeToCheck.BaseType;
        }
    }

Вот тест для демонстрации (основной) функциональности:

 [Test]
    public void SimpleGenericInterfaces()
    {
        Assert.IsTrue(typeof(Table<string>).IsOfGenericType(typeof(IEnumerable<>)));
        Assert.IsTrue(typeof(Table<string>).IsOfGenericType(typeof(IQueryable<>)));

        Type concreteType;
        Assert.IsTrue(typeof(Table<string>).IsOfGenericType(typeof(IEnumerable<>), out concreteType));
        Assert.AreEqual(typeof(IEnumerable<string>), concreteType);

        Assert.IsTrue(typeof(Table<string>).IsOfGenericType(typeof(IQueryable<>), out concreteType));
        Assert.AreEqual(typeof(IQueryable<string>), concreteType);


    }
Wiebe Tijsma
источник
0
return list.GetType().IsGenericType;
Стэн Р.
источник
3
Это правильно для другого вопроса. По этому вопросу он некорректен, поскольку решает только (значительно меньше) половину проблемы.
Groxx
1
Ответ Стэна R действительно отвечает на поставленный вопрос, но на самом деле OP имел в виду «Тестирование, если объект имеет определенный родовой тип в C #», для которого этот ответ действительно неполон.
yoyo
люди голосуют против меня, потому что я ответил на вопрос в контексте "является" универсальным типом, а не "имеет" универсальный тип. Английский - это мои вторые языки, и такие языковые нюансы часто пропускают меня, в мою защиту ОП специально не просил проверять определенный тип, и в заголовке спрашивается "имеет" универсального типа ... не знаю, почему я заслуживаю отрицательных голосов за неоднозначный вопрос.
Стэн Р.
2
Теперь вы знаете это, и вы можете улучшить свой ответ, чтобы быть более конкретным и правильным.
Петр Иван