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; }
}
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:
GetEnumerator
qui correspond au modèle requis.IEnumerable
et bien sûr, IEnumerable<T>
requiert IEnumerable
)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.
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 .
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 ()
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>();
}
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;
}