Testen, ob das Objekt in C # vom generischen Typ ist

134

Ich möchte einen Test durchführen, wenn ein Objekt von einem generischen Typ ist. Ich habe folgendes ohne Erfolg versucht:

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

Was mache ich falsch und wie führe ich diesen Test durch?

Richbits
quelle

Antworten:

201

Wenn Sie überprüfen möchten, ob es sich um eine Instanz eines generischen Typs handelt:

return list.GetType().IsGenericType;

Wenn Sie überprüfen möchten, ob es sich um ein Generikum handelt List<T>:

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

Wie Jon betont, überprüft dies die genaue Typäquivalenz. Rückgabe falsebedeutet nicht unbedingt list is List<T>Rückgabe false(dh das Objekt kann keiner List<T>Variablen zugewiesen werden).

Mehrdad Afshari
quelle
9
Das erkennt jedoch keine Subtypen. Siehe meine Antwort. Es ist auch viel schwieriger für Schnittstellen :(
Jon Skeet
1
Der Aufruf von GetGenericTypeDefinition wird ausgelöst, wenn es sich nicht um einen generischen Typ handelt. Stellen Sie sicher, dass Sie dies zuerst überprüfen.
Kilhoffer
85

Ich gehe davon aus, dass Sie nicht nur wissen möchten, ob der Typ generisch ist, sondern ob ein Objekt eine Instanz eines bestimmten generischen Typs ist, ohne die Typargumente zu kennen.

Es ist leider nicht besonders einfach. Es ist nicht schlecht, wenn der generische Typ eine Klasse ist (wie in diesem Fall), aber es ist schwieriger für Schnittstellen. Hier ist der Code für eine Klasse:

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

BEARBEITEN: Wie in den Kommentaren angegeben, funktioniert dies möglicherweise für Schnittstellen:

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

Ich habe den Verdacht, dass es einige unangenehme Randfälle gibt, aber ich kann keinen finden, für den es momentan fehlschlägt.

Jon Skeet
quelle
2
Habe gerade ein Problem damit entdeckt. Es geht nur eine einzige Vererbungslinie hinunter. Wenn Sie unterwegs eine Basis mit einer Basisklasse und der gesuchten Schnittstelle haben, wird dies nur über den Klassenpfad ausgeführt.
Groxx
1
@Groxx: Richtig. Ich habe gerade festgestellt, dass ich das in der Antwort erwähne: "Es ist nicht schlecht, wenn der generische Typ eine Klasse ist (wie in diesem Fall), aber es ist schwieriger für Schnittstellen. Hier ist der Code für eine Klasse"
Jon Skeet
1
Was ist, wenn Sie <T> nicht kennen? Es könnte int oder string sein, aber das wissen Sie nicht. Dies erzeugt anscheinend falsche Negative ... Sie müssen also kein T verwenden, sondern sehen sich nur die Eigenschaften eines Objekts an und eines ist eine Liste. Woher wissen Sie, dass es sich um eine Liste handelt, damit Sie sie lösen können? Damit meine ich, dass Sie weder ein T noch einen Typ verwenden können. Sie könnten jeden Typ erraten (ist es List <int>? Ist es List <string>?), Aber was Sie wissen möchten, ist IS THIS AA LIST? Diese Frage scheint schwer zu beantworten.
@ RiverC: Ja, Sie haben Recht - es ist aus verschiedenen Gründen ziemlich schwer zu beantworten. Wenn Sie nur über eine Klasse sprechen, ist es nicht schlecht ... Sie können weiter den Vererbungsbaum hinaufgehen und sehen, ob Sie List<T>in irgendeiner Form getroffen haben . Wenn Sie Schnittstellen einschließen, ist das wirklich schwierig.
Jon Skeet
3
Könnten Sie die Schleife nicht IsInstanceOfGenericTypedurch einen Aufruf von IsAssignableFromanstelle des Gleichheitsoperators ( ==) ersetzen ?
Slawekwin
7

Sie können kürzeren Code mit dynamischer Verwendung verwenden, obwohl dies langsamer sein kann als reine Reflexion:

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
quelle
7

Dies sind meine beiden bevorzugten Erweiterungsmethoden, die die meisten Randfälle der generischen Typprüfung abdecken:

Arbeitet mit:

  • Mehrere (generische) Schnittstellen
  • Mehrere (generische) Basisklassen
  • Hat eine Überladung, die den spezifischen generischen Typ "out" macht, wenn er true zurückgibt (siehe Unit-Test für Beispiele):

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

Hier ist ein Test, um die (grundlegende) Funktionalität zu demonstrieren:

 [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
quelle
0
return list.GetType().IsGenericType;
Stan R.
quelle
3
Es ist richtig für eine andere Frage. Diese Frage ist falsch, da nur die Hälfte des Problems (deutlich weniger als) behoben wird.
Groxx
1
Die Antwort von Stan R beantwortet tatsächlich die gestellte Frage, aber was das OP wirklich bedeutete, war "Testen, ob das Objekt in C # von einem bestimmten generischen Typ ist", für das diese Antwort tatsächlich unvollständig ist.
Jojo
Leute stimmen mich ab, weil ich die Frage im Zusammenhang mit "ist ein" generischer Typ und nicht "ist von einem" generischen Typ beantwortet habe. Englisch ist meine zweite Sprache und solche Sprachnuancen gehen oft an mir vorbei. Zu meiner Verteidigung hat das OP nicht ausdrücklich darum gebeten, gegen einen bestimmten Typ zu testen, und im Titel gefragt, ob es sich um einen generischen Typ handelt ... nicht sicher, warum ich Abstimmungen verdiene eine mehrdeutige Frage.
Stan R.
2
Jetzt wissen Sie das und können Ihre Antwort verbessern, um genauer und korrekter zu sein.
Peter Ivan