web-dev-qa-db-fra.com

Pourquoi foreach n'arrive-t-il pas à trouver ma méthode d'extension GetEnumerator?

J'essaie de rendre un code plus lisible. Par exemple, foreach(var row in table) {...} plutôt que foreach(DataRow row in table.Rows) {...}.

Pour ce faire, j'ai créé une méthode d'extension:

namespace System.Data {
    public static class MyExtensions {
        public static IEnumerable<DataRow> GetEnumerator( this DataTable tbl ) {
            foreach ( DataRow r in tbl.Rows ) yield return r;
        }
    }
}

Mais le compilateur lance toujours foreach statement cannot operate on variables of type 'System.Data.DataTable' because 'System.Data.DataTable' does not contain a public definition for 'GetEnumerator'.

Pour confirmer que j'ai correctement implémenté la méthode d'extension, j'ai essayé le code suivant et le compilateur n'a eu aucun problème avec cela.

for ( IEnumerator<DataRow> enm = data.GetEnumerator(); enm.MoveNext(); ) {
    var row = enm.Current;
    ...
}

Avant de dire que c'est parce que IEnumerator ou IEnumerator<DataRow> n'est pas implémenté, considérez que ce qui suit est compilé:

public class test {
    public void testMethod() {
        foreach ( var i in new MyList( 1, 'a', this ) ) { }
    }
}
public class MyList {
    private object[] _list;
    public MyList( params object[] list ) { _list = list; }
    public IEnumerator<object> GetEnumerator() { foreach ( var o in _list ) yield return o; }
}
20
jshall

Il y a beaucoup de confusion dans les autres réponses jusqu'à présent. (Bien que la réponse de Preston Guillot soit plutôt bonne, elle ne permet pas de comprendre ce qui se passe ici.) Permettez-moi de clarifier.

Firstoff, vous n'avez tout simplement pas de chance. C # requiert que la collection utilisée dans une déclaration foreach soit:

  1. Implémentez une variable publique GetEnumerator qui correspond au modèle requis.
  2. Implémentez IEnumerableet bien sûr, IEnumerable<T> requiert IEnumerable)
  3. Soyez dynamique. Dans ce cas, il suffit simplement de lancer la boîte de dialogue et d'effectuer l'analyse au moment de l'exécution.

Le résultat est que le type de collection doit implémenter réellement la GetEnumerator d'une manière ou d'une autre. Fournir une méthode d'extension ne la coupe pas.

C'est malheureux. À mon avis, lorsque l'équipe C # a ajouté des méthodes d'extension à C # 3, elle devrait avoir modifié les fonctionnalités existantes telles que foreach (et peut-être même using!) Afin de prendre en compte les méthodes d'extension. Cependant, le calendrier était extrêmement serré pendant le cycle de publication de la version C # 3 et toute tâche supplémentaire pour laquelle LINQ n'avait pas été implémenté à temps risquait d'être réduite. Je ne me souviens pas précisément de ce que l'équipe de conception a dit sur ce point et je n'ai plus mes notes. 

Cette situation regrettable résulte du fait que les langues grandissent et évoluent; les anciennes versions sont conçues pour les besoins de leur époque et les nouvelles versions doivent s'appuyer sur ces bases. Si, contre-factuellement, le C # 1.0 avait des méthodes d'extension et des génériques, la boucle foreach aurait pu être conçue comme LINQ: comme une simple transformation syntaxique. Mais ce n'était pas le cas, et nous sommes maintenant pris avec l'héritage de la conception pré-générique, de la méthode de pré-extension.

_ (Deuxième, il semble y avoir des informations erronées dans d’autres réponses et commentaires sur ce qui est précisément nécessaire pour que foreach fonctionne. Vous n'êtes pas obligé de mettre en œuvre IEnumerable. Pour plus de détails sur cette fonctionnalité souvent mal comprise, voir mon article sur l'objet .

Third, il semble y avoir une question de savoir si ce comportement est effectivement justifié par la spécification. C’est vrai. La spécification n’indique pas explicitement que les méthodes d’extension ne sont pas considérées dans ce cas, ce qui est regrettable. Cependant, la spécification est extrêmement claire sur ce qui se passe: 

Le compilateur commence par faire un membre lookupfor GetEnumerator. L'algorithme de recherche de membre est décrit en détail dans la section 7.3, et la recherche de membre ne prend pas en compte les méthodes d'extension, uniquement membres réels. Méthodes d'extension ne sont considérés que après l'échec de la résolution de surcharge normale}, et nous n'avons pas encore réussi à résoudre le problème de surcharge (et oui, les méthodes d'extension sont considérées par accès membre} _, mais membre access et recherche de membre sont des opérations différentes.)

Si la recherche de membre échoue pour trouver un groupe de méthodes, la tentative de correspondance avec le modèle échoue. Par conséquent, le compilateur ne passe jamais à la partie résolution de surcharge de l'algorithme et n'a donc jamais la possibilité d'envisager des méthodes d'extension.

Par conséquent, le comportement que vous décrivez est cohérent avec le comportement spécifié.

Je vous conseille de lire la section 8.8.4 de la spécification très soigneusement si vous voulez comprendre précisément comment un compilateur analyse une instruction foreach.

Quatrième, je vous encourage à passer votre temps à ajouter de la valeur à votre programme d’une autre manière. L’avantage incontestable de

foreach (var row in table)

plus de

foreach(var row in table.Rows)

est minuscule pour le développeur et invisible pour le client. Passez votre temps à ajouter de nouvelles fonctionnalités ou à corriger des bugs ou à analyser les performances, plutôt que de raccourcir de cinq caractères le code déjà parfaitement clair.

40
Eric Lippert

La méthode GetEnumerator de votre classe de test n'est pas statique, mais la méthode d'extension. Cela ne compile pas non plus:

class test
{
}

static class x
{
    public static IEnumerator<object> GetEnumerator(this test t) { return null; }
}

class Program
{
    static void Main(string[] args)
    {
        foreach (var i in new test()) {  }
    }
}

Pour que le sucre foreach syntax fonctionne, votre classe doit exposer une méthode publique GetEnumerator instance .

4
Preston Guillot

Dans une instruction foreach, le compilateur recherche une méthode d'instance de GetEnumerator. Par conséquent, le type (ici DataTable) doit implémenter IEnumerable. Il ne trouvera jamais votre méthode d'extension à la place, car elle est statique. Vous devez écrire le nom de votre méthode d'extension dans le foreach.

namespace System.Data {
    public static class MyExtensions {
        public static IEnumerable<DataRow> GetEnumerator( this DataTable table ) {
            foreach ( DataRow r in table.Rows ) yield return r;
        }
    }
}

foreach(DataRow row in table.GetEnumerator())
  .....

Pour éviter toute confusion, je suggère d'utiliser un nom différent pour votre méthode d'extension. Peut-être que quelque chose comme GetRows ()

0
Matthias

certains hors-sujet: si vous voulez faire plus d'écriture lisible

foreach ( DataRow r in tbl.Rows ) yield return r;

comme

foreach (DataRow row in tbl.Rows) 
{
    yield return row;
}

maintenant à votre problème .. essayez ceci

    public static IEnumerable<T> GetEnumerator<T>(this DataTable table)
    {
        return table.Rows.Cast<T>();
    }
0
Lucas

Votre extension est équivalente à:

    public static IEnumerable<TDataRow> GetEnumerator<TDataRow>( this DataTable tbl ) {
        foreach ( TDataRow r in tbl.Rows ) yield return r;
    }

GetEnumerator<TDataRow> n'est pas la même méthode que GetEnumerator

Cela fonctionnera mieux:

    public static IEnumerable<DataRow> GetEnumerator( this DataTable tbl ) {
        foreach (DataRow r in tbl.Rows ) yield return r;
    }
0
Amy B