2013-01-05 6 views
18

Sto cercando di rendere più leggibile il codice. Ad esempio foreach(var row in table) {...} anziché foreach(DataRow row in table.Rows) {...}.Perché foreach non riesce a trovare il mio metodo di estensione GetEnumerator?

Per fare questo ho creato un metodo di estensione:

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

Ma il compilatore tiri ancora foreach statement cannot operate on variables of type 'System.Data.DataTable' because 'System.Data.DataTable' does not contain a public definition for 'GetEnumerator'.

Per confermare che ho implementato il metodo di estensione in modo appropriato ho provato il seguente codice invece e il compilatore non ha avuto alcun problema con esso.

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

Prima di dire che è a causa IEnumerator o IEnumerator<DataRow> non è implementata, si consideri che il seguente fa compilare:

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

Mi sembra che il messaggio di errore dice tutto. È necessario implementarlo sulla classe, non come estensione. – spender

+1

Sono confuso .. che non può essere il codice reale. 'for'non funziona sull'enumeratore. E 'test' non è un enumerabile. Incolla il codice attuale –

+5

Nota molto soggettiva: creare codice che sembri comune (come 'var row in table.Rows') su un gran numero di file sorgente e di campioni in codice che * senti ora per essere più bello * non è necessario il modo migliore di creare codice più leggibile. 'Foreach' spesso può essere trasformato in istruzione LINQ che è più compatta e abbastanza comune da non mettere in discussione ciò che sta accadendo. Immagina di postare il tuo 'foreach (var row in table)' nella prossima domanda su SO - nessuno potrà ragionare su quel codice senza un campione più grande che includa le tue estensioni magiche. –

risposta

38

C'è molta confusione nelle altre risposte finora. (Anche se la risposta di Preston Guillot è piuttosto buona, in realtà non mette un dito su quello che sta succedendo qui.) Lasciami provare a chiarire.

Primo spento, siete semplicemente sfortunati. C# richiede che la raccolta utilizzata in un'istruzione foreach sia:

  1. Implementare un numero pubblico GetEnumerator corrispondente al modello richiesto.
  2. Implementare IEnumerable (e, naturalmente, richiede IEnumerable<T>IEnumerable)
  3. essere dinamico, in questo caso abbiamo semplicemente calci il barattolo lungo la strada e fare l'analisi in fase di esecuzione.

Il risultato è che il tipo di raccolta deve effettivamente implementare modo GetEnumerator uno o l'altro. Fornire un metodo di estensione non lo taglia.

Questo è un peccato. A mio parere, quando il team C# ha aggiunto i metodi di estensione al C# 3, dovrebbero aver modificato le funzionalità esistenti come foreach (e forse anche using!) Per prendere in considerazione i metodi di estensione. Tuttavia, la pianificazione era estremamente serrata durante il ciclo di rilascio del C# 3 e gli eventuali elementi di lavoro extra che non avevano implementato LINQ in tempo potrebbero essere tagliati. Non ricordo esattamente cosa ha detto il team di design su questo punto e non ho più i miei appunti.

Questa sfortunata situazione è il risultato del fatto che le lingue crescono e si evolvono; le vecchie versioni sono progettate per le esigenze del loro tempo e le nuove versioni devono basarsi su queste fondamenta. Se, controfattualmente, C# 1.0 aveva avuto metodi di estensione e generici, allora il ciclo foreach avrebbe potuto essere progettato come LINQ: come una semplice trasformazione sintattica. Ma non lo era, e ora siamo bloccati con il retaggio di pre-generico, design del metodo di pre-estensione.

Secondo, sembra esserci qualche disinformazione in altre risposte e commenti su ciò che è esattamente necessario per rendere operativo foreach. Non è necessario implementare IEnumerable. Per ulteriori dettagli su questa caratteristica comunemente fraintesa, vedere my article on the subject.

Terzo, sembra esserci qualche domanda sul fatto che questo comportamento sia effettivamente giustificato dalla specifica. È. Le specifiche non indicano esplicitamente che i metodi di estensione non sono considerati in questo caso, il che è sfortunato. Tuttavia, la specifica è estremamente chiara su cosa succede:

Il compilatore inizia eseguendo una ricerca membro per GetEnumerator. L'algoritmo di ricerca dei membri è documentato in dettaglio nella sezione 7.3 e la ricerca dei membri non considera i metodi di estensione, solo i membri effettivi . I metodi di estensione sono considerati solo dopo che la normale risoluzione di sovraccarico ha avuto esito negativo e non abbiamo ancora ottenuto la risoluzione di sovraccarico. (E sì, i metodi di estensione sono considerati da accesso membri, ma membri accesso e membro ricerca sono diverse operazioni.)

Se lookup membro non trovare un gruppo metodo poi il tentativo di abbinare il il modello fallisce.Il compilatore pertanto non passa mai alla parte della risoluzione di sovraccarico dell'algoritmo e pertanto non ha mai la possibilità di prendere in considerazione i metodi di estensione.

Pertanto, il comportamento che descrivi è coerente con il comportamento specificato.

io consiglio di leggere la sezione 8.8.4 della specifica molta attenzione se si vuole capire con precisione come un compilatore analizza una dichiarazione foreach.

Quarto, vi incoraggio a passare il vostro tempo aggiungendo valore al vostro programma in qualche altro modo. Il vantaggio convincente di

foreach (var row in table) 

oltre

foreach(var row in table.Rows) 

è piccolo per lo sviluppatore e invisibile al cliente. Trascorri il tuo tempo aggiungendo nuove funzionalità o correggendo bug o analizzando le prestazioni, piuttosto che abbreviare di cinque caratteri il codice già perfettamente chiaro.

0

alcuni offtopic: se si desidera farlo scrittura più leggibile

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

as

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

ora al vostro problema .. provare questo

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

Direi che il tuo primo punto è una questione di opinione, non di fatto. –

+0

Non c'è alcun problema con l'implementazione di GetEnumerator, il compilatore semplicemente non riesce a trovarlo. – jshall

+1

@jshall, non "non riesce a trovare", "non deve trovare" il metodo di estensione, - vedere il mio altro commento sulla posizione di questo requisito nella specifica C#. –

4

Il metodo GetEnumerator nella classe di test non è statico, il metodo di estensione è. Questo non può essere compilato sia:

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

Affinché lo zucchero sintassi foreach per lavorare la classe deve esporre un GetEnumerator pubblico esempio metodo.

+0

[email protected] jshall, Il comportamento del metodo di istanza di prelievo per l'iterazione è specificato nella sezione "8.8.4 L'istruzione foreach" di "CSharp Language Specification" per C# 4.0. Se lo si legge attentamente, non viene menzionato alcun metodo di estensione nella ricerca di "GetEnumerator". –

+0

-1 I metodi di estensione sono supposti per essere statici (consultare http://msdn.microsoft.com/en-us/library/bb311042.aspx). Inoltre, i metodi di estensione consentono di "aggiungere" metodi a tipi esistenti senza creare un nuovo tipo derivato, ricompilare o modificare in altro modo il tipo originale. (Vedere http://msdn.microsoft.com/en-us/library/vstudio /bb383977.aspx) – Trisped

+0

Naturalmente i metodi di estensione sono statici. Questo è il punto, GetEnumerator non può essere statico. –

0

tua estensione è pari a:

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

GetEnumerator<TDataRow> non è lo stesso metodo GetEnumerator

Questo funziona meglio:

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

Questo potrebbe "funzionare meglio" per risolvere il 'var' in' foreach (var row in table) 'ma non viene ancora compilato. – jshall

-6

La collezione oggetto in un foreach devono attuare System.Collections.IEnumerable o System.Collections.Generic.IEnumerable<T>.

Se si desidera attivare questa opzione, è possibile creare una classe wrapper che implementa IEnumerable e un puntatore a voi DataTable. In alternativa, è possibile ereditare DataTable in una nuova classe e implementare IEnumerable.

La leggibilità del codice è molto spesso una preferenza personale. Personalmente trovo le tue modifiche alla dichiarazione foreach meno leggibili (ma sono sicuro che ci sono molti su SO che sono d'accordo con te).

+0

Vedere la fine della domanda, l'ereditarietà non è necessaria e l'ultimo bit di codice lo dimostra. – jshall

+0

@jshall Sembra che abbia perso i collegamenti alla documentazione nella mia risposta. È necessariamente. Solo perché compila non significa che funzionerà, come hai già dimostrato. Se si è veramente interessati all'utilizzo di un 'DataTable' come raccolta di oggetti in un'istruzione' foreach', sarà necessario utilizzare una classe wrapper come suggerito o ereditare 'DataTable' in una nuova classe che implementa' IEnumerable'. – Trisped

+0

@jshall Credo che dovrei aggiungere il motivo per cui il tuo secondo esempio funziona perché il compilatore aggiunge l'interfaccia alla classe per te. Poiché non sta compilando la classe 'DataTable', non può aggiungere l'interfaccia nel tuo problema. L'altro problema con il tuo esempio è che non sta usando un'estensione per aggiungere il metodo. Dubito che sarebbe compilato se tu fossi. – Trisped

0

All'interno di un'istruzione foreach il compilatore sta cercando un metodo di istanza di GetEnumerator. Quindi il tipo (qui DataTable) deve implementare IEnumerable. Non troverà mai il tuo metodo di estensione, perché è statico. Devi scrivere il nome del tuo metodo di estensione nel 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()) 
    ..... 

Per evitare confusione, suggerirei di utilizzare un nome diverso per il metodo di estensione. Forse qualcosa come GetRows()