2008-09-16 8 views
22

exemple:Comment détecter si le type est un autre type générique

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

Cela peut-il être fait sans utiliser la réflexion? Comment puis-je dire IEnumerable en C#? Dois-je simplement utiliser IEnumerable puisque IEnumerable <> implémente IEnumerable?

Répondre

35

The previously accepted answer est agréable mais il est faux. Heureusement, l'erreur est petite. La vérification de IEnumerable n'est pas suffisante si vous voulez vraiment connaître la version générique de l'interface; il y a beaucoup de classes qui implémentent seulement l'interface non générique. Je vais donner la réponse dans une minute. Mais d'abord, je voudrais souligner que la réponse acceptée est trop compliquée, car le code suivant atteindre le même dans les circonstances données:

if (items[key] is IEnumerable) 

Cela fait encore plus parce que cela fonctionne pour chaque élément séparément (et non sur leur sous-classe commune, V).

Maintenant, pour la bonne solution. Ceci est un peu plus compliqué parce que nous devons prendre le type générique IEnumerable`1 (qui est, le type IEnumerable<> avec un paramètre de type) et injecter le droit l'argument générique:

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

Vous pouvez tester l'exactitude de ce code facilement :

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

rendements:

True 
False 

ne soyez pas trop préoccupé par le fait que celui-ci utilise la réflexion. Bien qu'il soit vrai que cela ajoute un temps d'exécution, l'utilisation de l'opérateur is l'est également.

Bien sûr, le code ci-dessus est terriblement contraint et pourrait être étendu à une méthode plus généralement applicable, IsAssignableToGenericType. La mise en œuvre suivante est légèrement incorrecte et je vais le laisser ici à des fins historiques seulement. Ne l'utilisez pas.Au lieu de cela, 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); 
} 

Il échoue lorsque le genericType est le même que givenType; pour la même raison, il échoue pour les types nullable, à savoir

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

J'ai créé un gist with a comprehensive suite of test cases.

+0

C'est exactement ce que je cherchais. Merci! Il utilise toujours la réflexion, mais je cherchais la syntaxe "typeof (IEnumerable <>)". Merci! –

+0

N'existe-t-il pas une autre façon de faire le contrôle sur le type, autre que d'utiliser "est"? –

+0

la ligne finale devrait lire 'return (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == genericType) || IsAssignableToGenericType (baseType, genericType); 'Vous ne pouvez pas éviter la récursivité juste parce que la classe de base est générique. –

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

Utilise la réflexion. Cela fonctionne ... mais je me demandais si vous pouviez le faire sans réflexion, surtout comment vous dites spécifier un type générique non spécialisé dans C#. Merci! –

+0

Je ne suis pas si sûr. Je pense que le runtime a suffisamment d'informations dans le système de type pour pouvoir répondre à la question IsAssignableFrom sans refléter l'assemblage. –

0

Je ne suis pas sûr de comprendre ce que vous voulez dire ici. Voulez-vous savoir si l'objet est un type générique ou si vous voulez tester s'il s'agit d'un type générique spécifique? Ou voulez-vous juste savoir si est énumérable?

Je ne pense pas que le premier est possible. La seconde est définitivement possible, il suffit de la traiter comme n'importe quel autre type. Pour le troisième, il suffit de le tester contre IEnumerable comme vous l'avez suggéré.

De même, vous ne pouvez pas utiliser l'opérateur 'is' sur les types.

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

Voir this question about types pour plus de détails. Peut-être que ça va t'aider.

+0

Je veux savoir si V dans mon exemple implémente IEnumerable de n'importe quoi, et je me fiche de quoi. J'aurais dû dire que les éléments [clé] est IEnumerable dans mon exemple. Merci d'avoir attrapé ça. –

1

J'utiliser la surcharge:

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

Un mot d'avertissement sur les types génériques et l'utilisation IsAssignableFrom() ...

Supposons que vous avez les éléments suivants:

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

public class MyItem : ItemBase 
{ 
} 

public class MyDerivedList : MyListBase<MyItem> 
{ 
} 

Appel IsAssignableFrom sur le type de liste de base ou sur le type de liste dérivée sera return false, mais clairement MyDerivedList hérite MyListBase<T>. (Une note rapide pour Jeff, génériques absolument doit être être enveloppé dans un bloc de code ou tildes pour obtenir le <T>, sinon il est omis.Est-ce prévu?) Le problème provient du fait que MyListBase<MyItem> est traité comme un type entièrement différent de MyListBase<T>. L'article suivant pourrait expliquer cela un peu mieux. http://mikehadlow.blogspot.com/2006/08/reflecting-generics.html

Au lieu de cela, essayez la fonction récursive suivante:

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: nouveau poste de Konrad qui prend la récursion générique en compte ainsi que des interfaces est sur place. Très bon travail. :)

/EDIT2: Si une vérification est faite pour savoir si genericType est une interface, les avantages en termes de performances pourraient être réalisés. Le contrôle peut être un si bloc autour du code actuel d'interface, mais si vous êtes intéressé à utiliser .NET 3.5, un de mes amis offre les services suivants:

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

Re remarque à Jeff: C'est par conception parce que Markdown permet le HTML en ligne. Merci pour votre avis, je vais l'intégrer dans mon code. –

+0

Rich, je préfère réellement votre code à la mienne mais malheureusement, cela ne fonctionne pas si 'genericType' est un type d'interface comme en question. Cela peut être facilement réparé, cependant. –

+0

Voir la réponse de James pour une meilleure réponse: http://stackoverflow.com/a/1075059/320623 – Joel

0

Je pensais que cette situation peut être résoluble d'une manière similaire à @Thomas Danecker de solution, mais en ajoutant un autre argument de modèle:

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

mais je remarqué maintenant qu'il does't travail à moins que je précise les arguments du modèle de la première méthode explicitement. Ceci n'est clairement pas personnalisé pour chaque élément du dictionnaire, mais il peut s'agir d'une sorte de solution de pauvre.

Je serais très reconnaissant si quelqu'un pouvait signaler quelque chose d'incorrect que j'aurais pu faire ici.

82

Merci beaucoup pour cet article. Je voulais fournir une version de la solution de Konrad Rudolph qui a mieux fonctionné pour moi. J'ai eu des problèmes mineurs avec cette version, notamment lors du test si un type est un type de valeur nullable:

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

Excellent travail! Voici une version courte (er) pour les personnes qui préfèrent la brièveté: 'public static bool IsAssignableToGenericType (ce type de type, type genericType) { return type.GetInterfaces(). Tout (it => it.IsGenericType && it.GetGenericTypeDefinition () == genericType) || (type.IsGenericType && type.GetGenericTypeDefinition() == genericType) || (type.BaseType! = null && IsAssignableToGenericType (type.BaseType, genericType)); } ' – Moeri

+3

C'est génial mais il semble que ce serait beaucoup plus rapide si vous déplaciez la comparaison directe au-dessus de la comparaison d'interface si la comparaison directe correspondait? – Shazwazza

+1

@Moeri Rendez-le encore plus court en éliminant le prédicat redondant et en utilisant '? .':' public static bool IsAssignableToGenericType (ce type Type, Type générique) {return new [] {type} .Concat (type.GetTypeInfo(). ImplémentéInterfaces) .Any (i => i.IsConstructedGenericType && i.GetGenericTypeDefinition() == générique) || (type.GetTypeInfo(). BaseType? .IsAssignableToGenericType (générique) ?? false); } ' – HappyNomad

4

Merci pour la grande information. Pour la commodité, j'ai refactored ceci dans une méthode d'extension et l'ai réduite à une déclaration simple.

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

Maintenant, il peut être facilement appelé avec:

sometype.IsAssignableToGenericType (typeof (MyGenericType <>))

+0

+1 pour une ligne –

Questions connexes