2010-07-09 23 views
4

J'ai la classe de classe et l'extension suivante (pour cet exemple):Conversion de IEnumerable <T> pour Méthode d'extension Problème

public class Person<T> 
{ 
    public T Value { get; set; } 
} 

public static class PersonExt 
{ 
    public static void Process<TResult>(this Person<IEnumerable<TResult>> p) 
    { 
     // Do something with .Any(). 
     Console.WriteLine(p.Value.Any()); 
    } 
} 

Je me attendais que je pouvais écrire quelque chose comme ce qui suit et cela fonctionnerait, mais il doesn « t:

var x = new Person<List<String>>(); 
x.Process(); 

Depuis la liste est plus faible dans l'arbre d'héritage que IEnumerable, ne devrait pas être valide ce? Cela fonctionne si je suis un Person<IEnumerable<String>> bien sûr parce que c'est le type direct. J'essaie d'utiliser une méthode d'extension qui peut être appliquée à tous les Person<T> tant que T implémente IEnumerable<Something> parce que j'ai besoin d'utiliser la méthode .Any().

EDIT: Peut-être que ma compréhension de la covariance est désactivée? Je sais IEnumerable<String> devrait convertir en IEnumerable<Object>, mais ne pouvait pas IList<String> convertir en IEnumerable<String>?

EDIT2: J'ai oublié de mentionner que je suis en utilisant .net 4.0.

+0

En ce qui concerne votre édition, ce sont des concepts différents, 'IList ' implémente 'ICollection ' qui implémente 'IEnumerable ', c'est la raison pour laquelle la conversion, il est permis, il est pas nécessaire pour qu'il soit covariant ou contravariant dans ce Cas. –

+0

Oui, je comprends * maintenant * à propos de co/contravariance après avoir lu quelques dernières minutes. Alors, pourquoi ce que j'ai écrit n'est pas valide? Il semble intuitif que ce serait ... – TheCloudlessSky

+1

Si vous faisiez cela en C# 4.0, je supposerais que ça marcherait, mais pas avec C# 3.0 et moins. –

Répondre

2

Je sais IEnumerable<String> devrait convertir en IEnumerable<Object>, mais ne pouvait IList<String> convertir en IEnumerable<String>?

IList<String> peut convertir en IEnumerable<String>. Le problème est que vous essayez de convertir Person<List<String>> en Person<IEnumerable<String>>, ce qui est illégal. Par exemple, il est parfaitement valide pour écrire:

var x = new Person<IEnumerable<String>>(); 
x.Value = new string[0]; 

puisque la valeur est de type IEnumerable<String> et un tableau de chaînes est un IEnumerable<String>. Cependant, vous ne pouvez pas écrire:

var x = new Person<List<String>>(); 
x.Value = new string[0]; 

puisque la valeur est de type List<String>. Puisque vous ne pouvez pas utiliser un Person<List<String>> dans tous les endroits où vous pouvez utiliser un Person<IEnumerable<String>>, ce n'est pas une distribution légale.

Notez que vous pouvez faire quelque chose de semblable à ce que vous voulez si vous ajoutez un deuxième paramètre de type à votre méthode d'extension:

public static void Process<TResult, TList>(this Person<TList> p) 
    where TList : IEnumerable<TResult> 
{ 
    Console.WriteLine(p.Value.Any()); 
} 

Malheureusement, le compilateur ne sera pas en mesure de déduire les paramètres de type, de sorte vous devrez l'appeler comme ceci:

var x = new Person<List<String>>(); 
x.Process<String, List<String>>(); 

Si vous utilisez C# 4.0 et peut utiliser covariance, vous pouvez définir une interface covariant pour personne:

public interface IPerson<out T> 
{ 
    T Value { get; } 
} 

public class Person<T> 
    : IPerson<T> 
{ 
    public T Value { get; set; } 
} 

Et puis rédigez votre méthode d'extension comme:

public static void Process<TResult>(this IPerson<IEnumerable<TResult>> p) 
{ 
    // Do something with .Any(). 
    Console.WriteLine(p.Value.Any()); 
} 

Depuis IPerson<T>.Value est en lecture seule, un IPerson<List<String>> pouvez être utilisé partout où un IPerson<IEnumerable<String>> peut être, et la conversion est valide.

+0

Ouais je manquais juste le mot-clé 'out' sur l'interface. Merci! – TheCloudlessSky

0

Vous parlez de covariance, mais ne l'utilisez pas réellement. Vous devez spécifier in ou out sur vos paramètres génériques. Notez que co/contravariance ne fonctionne pas sur les types de classe; ils doivent être appliqués aux interfaces.

Ainsi, l'introduction d'une interface et d'en faire Covariant:

public interface IPerson<out T> 
{ 
    T Value { get; } 
} 

public class Person<T> : IPerson<T> 
{ 
    public T Value { get; set; } 
} 

public static class PersonExt 
{ 
    public static void Process<TResult>(this IPerson<IEnumerable<TResult>> p) 
    { 
    // Do something with .Any(). 
    Console.WriteLine(p.Value.Any()); 
    } 
} 

ce code permet de compiler:

var x = new Person<List<String>>(); 
x.Process(); 
1

Je ne suis pas sûr que vous avez tout à fait compris l'utilisation correcte des médicaments génériques. Dans tous les cas ...

La seule chose qui est incorrecte est votre déclaration de méthode d'extension, et la façon dont vous essayez de contraindre la méthode d'extension.

public static class ThingExtensions 
{ 
    public static void Process<T>(this Thing<T> p) 
     where T : IEnumerable<string> 
    { 
     // Do something with .Any(). 
     Console.WriteLine(p.Value.Any()); 
    } 
} 

Tout ce que je l'ai vraiment fait est renomme Person à Thing pour que nous ne sommes pas enferrons sur quel Person<List<string>> est vraiment.

public class Thing<T> 
{ 
    public T Value { get; set; } 
} 

class ListOfString : List<string> 
{ } 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var x = new Thing<ListOfString>(); 
     x.Value = new ListOfString(); 
     x.Process(); 

     x.Value.Add("asd"); 
     x.Process(); 


     var x2 = new Thing<int>(); 
     // Error 1 The type 'int' cannot be used as type parameter 'T' 
     // in the generic type or method 
     // 'ThingExtensions.Process<T>(Thing<T>)'. 
     // There is no boxing conversion from 'int' to 
     // 'System.Collections.Generic.IEnumerable<string>'.  
     //x2.Process(); 

     Console.Read(); 
    } 
} 

Vous pouvez également déplacer la contrainte générique au Thing<T> si cela était plus applicable.

+0

J'aurais dû mentionner que c'est une application de console avec .net 3.5 (VS 2008) –

+0

Vous pouvez également simplement remplacer 'ListOfString' par' Liste 'ou par tout ce qui implémente' IEnumerable ' –