Тестування, якщо об'єкт має загальний тип у C #


134

Я хотів би виконати тест, якщо об’єкт має загальний тип. Я безрезультатно намагався:

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

Що я роблю неправильно і як мені виконати цей тест?

Відповіді:


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

EDIT: Як зазначено в коментарях, це може працювати для інтерфейсів:

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

У мене є підозрілі припущення, що навколо цього можуть бути деякі незручні випадки, але я не можу знайти жодного, який зараз не виходить з ладу.


2
Щойно виявили проблему з цим. Це йде лише по одній лінії спадкування. Якщо попутно у вас є база як з базовим класом, так і з інтерфейсом, який ви шукаєте, це знижується лише шляхом класу.
Groxx

1
@Groxx: Правда. Я щойно помітив, що я згадую це у відповіді: "Це не дуже погано, якщо загальний тип - це клас (як це в даному випадку), але це складніше для інтерфейсів. Ось код для класу"
Джон Скіт

1
Що робити, якщо у вас немає способу пізнати <T>? Мовляв, це може бути int або string, але ви цього не знаєте. Це породжує, здавалося б, хибні негативи ... тому у вас немає Т для використання, ви просто переглядаєте властивості якогось об'єкта, і один - це список. Звідки ви знаєте, що це список, щоб ви могли його скасувати? Під цим я маю на увазі, у вас немає ніде Т, ні типу. Ви можете здогадатися про кожен тип (це список <int>? Це список <рядок>?), Але що ви хочете знати, це ЦЕ СПИСОК АА? На це питання здається важко відповісти.

@RiverC: Так, ви маєте рацію - це досить важко відповісти, з різних причин. Якщо ви говорите лише про клас, це не так вже й погано ... ви можете продовжувати ходити по спадковому дереву і бачити, чи потрапили ви List<T>в ту чи іншу форму. Якщо включити інтерфейси, це справді складно.
Джон Скіт

3
Ви не можете замінити цикл IsInstanceOfGenericTypeна виклик IsAssignableFromзамість оператора рівності ( ==)?
slawekwin

7

Ви можете використовувати коротший код, використовуючи динамічний althougth, це може бути повільніше, ніж чисте відображення:

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

Це два моїх улюблених способу розширення, які охоплюють більшість кращих випадків перевірки загального типу:

Працює з:

  • Кілька (загальних) інтерфейсів
  • Кілька (загальних) базових класів
  • Має перевантаження, яка буде "виводити" конкретний загальний тип, якщо він повертає істину (див. Тест одиниці для зразків):

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


    }

0
return list.GetType().IsGenericType;

3
Це правильно для іншого питання. Для цього питання це неправильно, оскільки воно вирішує лише (значно менше) половини проблеми.
Groxx

1
Відповідь Стен Р насправді відповідає на поставлене запитання, але те, що дійсно означало ОП, було "Тестування, якщо об'єкт має певний родовий тип на C #", для якого ця відповідь справді неповна.
yoyo

люди мене голосують, тому що я відповів на питання в контексті "є" родовим типом, а не "є" родовим. Англійська мова є моєю другою мовою, і такі мовні нюанси передають мене часто, на мій захист ОП спеціально не просив тестувати певний тип і в заголовку запитує "має" родовий тип ... не впевнений, чому я заслуговую скорочення на неоднозначне запитання.
Стен Р.

2
Тепер ви це знаєте, і можете покращити свою відповідь, щоб бути більш конкретним і правильним.
Петро Іван
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.