为什么foreach无法找到我的GetEnumerator扩展方法?

我试图使一些代码更具可读性。 例如foreach(var row in table) {...}而不是foreach(DataRow row in table.Rows) {...}

为此,我创建了一个扩展方法:

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

但是编译器仍然抛出foreach statement cannot operate on variables of type 'System.Data.DataTable' because 'System.Data.DataTable' does not contain a public definition for 'GetEnumerator'

为了确认我已经适当地实现了扩展方法,我尝试了以下代码,编译器没有问题。

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

在您说这是因为未实现IEnumeratorIEnumerator ,请考虑以下内容进行编译:

 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 GetEnumerator() { foreach ( var o in _list ) yield return o; } } 

到目前为止,其他答案中存在许多混淆。 (虽然Preston Guillot的答案非常好,但实际上并没有指出这里发生的事情。)让我试着澄清一下。

首先 ,你只是运气不好。 C#要求foreach语句中使用的集合:

  1. 实现与所需模式匹配的公共GetEnumerator
  2. 实现IEnumerable (当然, IEnumerable需要IEnumerable
  3. 保持动态,在这种情况下,我们只需将jar子放在路上并在运行时进行分析。

结果是集合类型必须以某种方式实现 GetEnumerator 。 提供扩展方法不会削减它。

这很不幸。 在我看来,当C#团队向C#3添加扩展方法时,他们应该修改现有的function,例如foreach (甚至可能using !)来考虑扩展方法。 但是,在C#3发布周期中,时间表非常紧张,并且任何未及时实现LINQ的额外工作项都可能被削减。 我不记得设计团队在这一点上说了什么,我再也没有笔记了。

这种不幸的情况是语言成长和发展的结果; 旧版本是为他们的时间需求而设计的,新版本必须建立在这个基础之上。 如果,反事实地,C#1.0有扩展方法和generics,那么foreach循环可以像LINQ一样设计:作为简单的句法转换。 但事实并非如此,现在我们仍然坚持预先通用的预扩展方法设计。

其次 ,在其他答案和评论中似乎存在一些错误的信息,这些答案和评论是关于使foreach工作所需的精确内容。 您不需要实现IEnumerable 。 有关这个常被误解的function的更多详细信息,请参阅我关于此主题的文章 。

第三 ,关于这种行为是否真的被规范certificate是合理的,似乎存在一些问题。 它是。 规范没有明确地指出在这种情况下不考虑扩展方法,这是不幸的。 但是,规范非常明确:

编译器首先对GetEnumerator进行成员查找 。 成员查找算法在7.3节中详细记录, 成员查找不考虑扩展方法 ,只考虑 实际成员 。 仅在常规重载解析失败后才考虑扩展方法,并且我们还没有超载解决方案。 (是的,扩展方法被成员访问考虑,但成员访问成员查找是不同的操作。)

如果成员查找未能找到方法组,则匹配该模式的尝试将失败。 因此编译器永远不会继续算法的重载解析部分,因此永远不会有机会考虑扩展方法。

因此,您描述的行为与指定的行为一致。

如果您想要准确理解编译器如何分析foreach语句,我建议您仔细阅读规范的第8.8.4节。

第四 ,我鼓励您花时间以其他方式为您的计划增加价值。 令人信服的好处

 foreach (var row in table) 

过度

 foreach(var row in table.Rows) 

对开发人员来说很小,对客户来说是不可见的。 花时间添加新function或修复错误或分析性能,而不是将已经完全清晰的代码缩短五个字符。

测试类中的GetEnumerator方法不是静态的,扩展方法是。 这不会编译:

 class test { } static class x { public static IEnumerator GetEnumerator(this test t) { return null; } } class Program { static void Main(string[] args) { foreach (var i in new test()) { } } } 

为了使foreach语法糖起作用,您的类必须公开一个公共GetEnumerator 实例方法。

一些offtopic:如果你想做更可读的写

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

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

现在你的问题..试试这个

  public static IEnumerable GetEnumerator(this DataTable table) { return table.Rows.Cast(); } 

您的扩展程序相当于:

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

GetEnumeratorGetEnumerator方法不同

这将更好地工作:

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

在foreach语句中,编译器正在寻找GetEnumerator的实例方法。 因此类型(此处为DataTable)必须实现IEnumerable。 它永远不会找到你的扩展方法,因为它是静态的。 您必须在foreach中写下扩展方法的名称。

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

为避免混淆,我建议您为扩展方法使用不同的名称。 也许像GetRows()

foreach的对象集合必须实现System.Collections.IEnumerableSystem.Collections.Generic.IEnumerable

如果您非常强烈地希望启用此function,那么您可以创建一个实现IEnumerable的包装器类,并具有指向DataTable的指针。 或者,您可以在新类中inheritanceDataTable并实现IEnumerable

代码可读性通常是个人偏好。 我个人觉得你对foreach语句的更改不太可读(但我相信很多人都同意你的意见)。