2008-09-16 10 views
22

Beispiel ist:Wie erkennen, ob Typ Ein weiterer Allgemein Typ

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 */ } 
} 

Kann dies ohne Reflexion zu tun? Wie sage ich IEnumerable in C#? Soll ich IEnumerable verwenden, da IEnumerable <> IEnumerable implementiert?

Antwort

35

The previously accepted answer ist schön, aber es ist falsch. Zum Glück ist der Fehler ein kleiner. Die Überprüfung auf IEnumerable ist nicht genug, wenn Sie wirklich über die generische Version der Schnittstelle informiert werden möchten; Es gibt viele Klassen, die nur die nicht generische Schnittstelle implementieren. Ich werde die Antwort in einer Minute geben. Zunächst aber möchte ich darauf hinweisen, dass die akzeptierte Antwort zu kompliziert ist, da der folgende Code das gleiche unter den gegebenen Umständen erreichen würde:

if (items[key] is IEnumerable) 

Dies gilt noch mehr, weil sie separat für jedes Element funktioniert (und nicht auf ihre gemeinsame Unterklasse, V).

Jetzt für die richtige Lösung. Dies ist ein wenig komplizierter, weil wir den generischen Typ IEnumerable`1 nehmen (das heißt, der Typ IEnumerable<> mit einem Typ-Parameter) und injizieren das Recht allgemeine Argument:

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

Sie die Richtigkeit dieser Code testen können leicht :

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

ergibt:

True 
False 

übermäßig besorgt über die Tatsache werden Sie nicht, dass diese Reflexion verwendet. Es ist zwar wahr, dass dies einen Laufzeit-Overhead hinzufügt, aber auch die Verwendung des Operators is.

Natürlich ist der obige Code furchtbar eingeschränkt und könnte in eine allgemein anwendbare Methode, IsAssignableToGenericType erweitert werden. Die folgende Implementierung ist etwas falsch und ich lasse es hier nur für historische Zwecke. Nicht verwenden.Stattdessen James has provided an excellent, correct implementation in his answer.

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

Es schlägt fehl, wenn die genericType die gleiche wie givenType ist; Aus dem gleichen Grund, versagt es für Nullable Types, das heißt

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

ich eine gist with a comprehensive suite of test cases erstellt haben.

+0

Genau das habe ich gesucht. Vielen Dank! Es verwendet immer noch die Reflexion, aber ich suchte nach der Syntax "typeof (IEnumerable <>)". Vielen Dank! –

+0

Gibt es keine andere Möglichkeit, den Typ zu überprüfen, als "is" zu verwenden? –

+0

Die letzte Zeile sollte 'return (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == genericType) || lesen IsAssignableToGenericType (baseType, genericType); 'Sie können die Rekursion nicht vermeiden, nur weil die Basisklasse generisch ist. –

4
if (typeof(IEnumerable).IsAssignableFrom(typeof(V))) { 
+0

Das verwendet Reflexion. Es funktioniert ... aber ich frage mich, ob Sie es ohne Reflexion tun können, vor allem wie Sie sagen, einen unspezialisierten generischen Typ in C# angeben. Vielen Dank! –

+0

Ich bin mir nicht so sicher. Ich denke, dass die Laufzeit genügend Informationen in dem Typsystem hat, um die IsAssignableFrom-Frage zu beantworten, ohne die Assembly zu reflektieren. –

0

Ich bin mir nicht sicher, ich verstehe, was Sie hier meinen. Möchten Sie wissen, ob das Objekt von generischen Typ ist oder möchten Sie testen, ob es sich um einen spezifischen generischen Typ handelt? Oder willst du nur wissen, ob es aufzählbar ist?

Ich glaube nicht, dass der erste möglich ist. Der zweite ist definitiv möglich, behandle ihn wie jeden anderen Typ. Testen Sie es als drittes gegen IEnumerable, wie Sie es vorgeschlagen haben.

Außerdem können Sie den 'is'-Operator für Typen nicht verwenden.

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

Weitere Details finden Sie unter this question about types. Vielleicht hilft es dir.

+0

Ich möchte wissen, ob V in meinem Beispiel IEnumerable von etwas implementiert, und es ist mir egal was. Ich hätte gesagt, dass Artikel [Schlüssel] IEnumerable in meinem Beispiel ist. Danke, dass Sie das verstanden haben. –

1

ich Überlastung verwenden würde:

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 */ }); 
} 
5

Ein Wort der Warnung über generische Typen und mit IsAssignableFrom() ...

Angenommen, Sie haben die folgenden:

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

public class MyItem : ItemBase 
{ 
} 

public class MyDerivedList : MyListBase<MyItem> 
{ 
} 

auf der Basis Listentyp oder auf der abgeleiteten Listentyp aufrufen IsAssignableFrom wird return false, aber eindeutig MyDerivedList erbt MyListBase<T>. (Eine kurze Notiz für Jeff, Generika absolut muss in einem Codeblock oder Tilden gewickelt werden, um die <T> zu bekommen, sonst ist es weggelassen. Ist das beabsichtigt?) Das Problem rührt von der Tatsache, dass MyListBase<MyItem> als ein ganz anderer Typ behandelt wird MyListBase<T>. Der folgende Artikel könnte das ein wenig besser erklären. Statt http://mikehadlow.blogspot.com/2006/08/reflecting-generics.html

, versuchen Sie die folgende rekursive Funktion:

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: Konrads neuen Beitrag, der auch die allgemeine Rekursion berücksichtigt als Schnittstellen genau das Richtige ist. Sehr gute Arbeit. :)

/EDIT2: Wenn eine Überprüfung vorgenommen wird, ob genericType eine Schnittstelle ist, könnten Leistungsvorteile realisiert werden. Die Prüfung kann eine sein, wenn Block um den aktuellen Interface-Code, aber wenn Sie sich mit der .NET 3.5, ein Freund von mir bietet folgendes interessieren:

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); 
    } 
+0

Hinweis zu Jeff: Dies ist beabsichtigt, da Markdown Inline-HTML ermöglicht. Vielen Dank für Ihre Nachricht, ich werde es in meinen Code integrieren. –

+0

Rich, ich bevorzuge eigentlich deinen Code zu meinem, aber leider funktioniert es nicht, wenn 'genericType' ein Interface-Typ ist wie in Frage. Dies kann jedoch leicht behoben werden. –

+0

Siehe James 'Antwort für eine bessere Antwort: http://stackoverflow.com/a/1075059/320623 – Joel

0

ich eine solche Situation zu denken verwendet auflösbar sein kann in ähnlicher Weise wie @Thomas Danecker der solution, aber das Hinzufügen eines weiteren Template-Argument:

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 */ } 

aber ich merkte jetzt, dass es Arbeit does't, wenn ich die Template-Argumente des ersten Verfahrens explizit angeben. Dies ist eindeutig nicht für jeden Artikel im Wörterbuch angepasst, aber es kann eine Art Lösung des armen Mannes sein.

Ich wäre sehr dankbar, wenn jemand etwas falsches darauf hinweisen könnte, dass ich hier getan hätte.

82

Vielen Dank für diesen Beitrag. Ich wollte eine Version von Konrad Rudolphs Lösung anbieten, die für mich besser funktioniert hat. Ich hatte kleinere Probleme mit dieser Version, vor allem bei der Prüfung, ob ein Typ ein Nullable-Wertetyp:

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); 
} 
+1

Tolle Arbeit! Hier ist eine kurze (er) Version für Leute, die die Kürze bevorzugen: 'public static bool IsAssignableToGenericType (diese Art von Typ, Type genericType) { Rückgabetyp.GetInterfaces(). Any (it => it.IsGenericType && it.GetGenericTypeDefinition () == genericType) || (type.IsGenericType && type.GetGenericTypeDefinition() == genericType) || (type.BaseType! = null && IsAssignableToGenericType (type.BaseType, genericType)); } ' – Moeri

+3

Das ist großartig, aber es sieht so aus, als wäre es viel schneller, wenn Sie den direkten Vergleich über den Schnittstellenvergleich verschieben, wenn der direkte Vergleich übereinstimmt? – Shazwazza

+1

@Moeri Machen Sie es noch kürzer, indem Sie das redundante Prädikat eliminieren und '? .' verwenden: public static bool IsAssignableToGenericType (Typ Typ, Typ generisch) {return new [] {Typ} .Concat (type.GetTypeInfo(). ImplementierteInterfaces) .Any (i => i.IsConstructedGenericType && i.GetGenericTypeDefinition() == generic) || (type.GetTypeInfo(). BaseType? .IsAssignableToGenericType (generisch) ?? false); } ' – HappyNomad

4

Danke für die tolle Info. Aus Gründen der Benutzerfreundlichkeit habe ich dies in eine Erweiterungsmethode umgewandelt und auf eine einzige Anweisung reduziert.

sometype:

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

Jetzt kann es leicht mit aufgerufen werden.IsAssignableToGenericType (typeof (MyGenericType <>))

+0

+1 für Einzeiler –

Verwandte Themen