Testowanie, czy obiekt jest typu ogólnego w C #

134

Chciałbym przeprowadzić test, jeśli obiekt jest typu ogólnego. Próbowałem następujących bez powodzenia:

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

Co robię źle i jak mam wykonać ten test?

Richbits
źródło

Odpowiedzi:

201

Jeśli chcesz sprawdzić, czy jest to instancja typu ogólnego:

return list.GetType().IsGenericType;

Jeśli chcesz sprawdzić, czy jest to generyczny List<T>:

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

Jak wskazuje Jon, sprawdza to dokładną równoważność typów. Powrót falseniekoniecznie oznacza list is List<T>zwrot false(tj. Obiekt nie może być przypisany do List<T>zmiennej).

Mehrdad Afshari
źródło
9
To jednak nie wykryje podtypów. Zobacz moją odpowiedź. Jest też znacznie trudniej w przypadku interfejsów :(
Jon Skeet,
1
Wywołanie GetGenericTypeDefinition spowoduje zgłoszenie, jeśli nie jest to typ ogólny. Upewnij się, że najpierw to sprawdzisz.
Kilhoffer,
85

Zakładam, że nie chcesz tylko wiedzieć, czy typ jest ogólny, ale jeśli obiekt jest wystąpieniem określonego typu ogólnego, bez znajomości argumentów typu.

Niestety nie jest to strasznie proste. Nie jest źle, jeśli typ generyczny jest klasą (tak jak w tym przypadku), ale jest to trudniejsze w przypadku interfejsów. Oto kod zajęć:

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

EDYCJA: Jak zauważono w komentarzach, może to działać w przypadku interfejsów:

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

Podejrzewam, że mogą być wokół tego jakieś niezręczne skrajne przypadki, ale nie mogę znaleźć takiego, który na razie zawodzi.

Jon Skeet
źródło
2
Właśnie odkryłem problem z tym. Spada tylko jedną linię dziedziczenia. Jeśli po drodze masz bazę zawierającą zarówno klasę bazową, jak i interfejs, którego szukasz, jest to tylko ścieżka klasowa.
Groxx
1
@Groxx: Prawda. Właśnie zauważyłem, że wspominam o tym w odpowiedzi: „Nie jest tak źle, jeśli typ ogólny jest klasą (jak to jest w tym przypadku), ale jest trudniejszy w przypadku interfejsów. Oto kod klasy”
Jon Skeet
1
A co jeśli nie masz sposobu, aby poznać <T>? Na przykład może to być int lub string, ale tego nie wiesz. To generuje, wydaje się, fałszywe negatywy ... więc nie masz T do użycia, po prostu przeglądasz właściwości jakiegoś obiektu, a jeden to lista. Skąd wiesz, że to lista, więc możesz ją usunąć? Rozumiem przez to, że nigdzie nie masz T ani typu do użycia. Możesz odgadnąć każdy typ (czy to List <int>? Czy to List <string>?), Ale to, co chcesz wiedzieć, to JEST TO LISTA AA? Trudno odpowiedzieć na to pytanie.
@RiverC: Tak, masz rację - odpowiedź jest dość trudna z różnych powodów. Jeśli mówisz tylko o klasie, nie jest tak źle ... możesz iść dalej po drzewie spadku i sprawdzić, czy trafiłeś List<T>w takiej czy innej formie. Jeśli dołączysz interfejsy, jest to naprawdę trudne.
Jon Skeet
3
czy nie mógłbyś zamienić pętli IsInstanceOfGenericTypena wywołanie IsAssignableFromzamiast operatora równości ( ==)?
slawekwin
7

Możesz użyć krótszego kodu, używając dynamicznego, chociaż może to być wolniejsze niż czyste odbicie:

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();
David Desmaisons
źródło
7

Oto moje dwie ulubione metody rozszerzeń, które obejmują większość przypadków skrajnych sprawdzania typów ogólnych:

Pracuje z:

  • Wiele (ogólnych) interfejsów
  • Wiele (ogólnych) klas bazowych
  • Ma przeciążenie, które `` wyłączy '' określony typ ogólny, jeśli zwróci wartość true (zobacz test jednostkowy dla próbek):

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

Oto test demonstrujący (podstawową) funkcjonalność:

 [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
źródło
0
return list.GetType().IsGenericType;
Stan R.
źródło
3
To poprawne na inne pytanie. W przypadku tego pytania jest niepoprawne, ponieważ dotyczy tylko (znacznie mniej niż) połowy problemu.
Groxx
1
Odpowiedź Stana R faktycznie odpowiada na postawione pytanie, ale OP naprawdę miał na myśli „Testowanie, czy obiekt jest określonego typu ogólnego w C #”, dla którego ta odpowiedź jest rzeczywiście niekompletna.
yoyo
ludzie głosują na mnie negatywnie, ponieważ odpowiedziałem na pytanie w kontekście „jest” typem ogólnym, a nie „jest” typem ogólnym. Angielski jest moim drugim językiem i takie niuanse językowe często mnie omijają, na moją obronę OP nie prosiło specjalnie o testowanie z określonym typem, aw tytule pyta „jest” typu ogólnego ... nie wiem, dlaczego zasługuję na głosy przeciw niejednoznaczne pytanie.
Stan R.
2
Teraz już to wiesz i możesz poprawić swoją odpowiedź, aby była bardziej szczegółowa i poprawna.
Peter Ivan,