21

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

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

namespace System.Data {
    public static class MyExtensions {
        public static IEnumerable<DataRow> 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<DataRow> enm = data.GetEnumerator(); enm.MoveNext(); ) {
    var row = enm.Current;
    ...
}

在您说这是因为IEnumerator还是IEnumerator<DataRow>未实现之前,请考虑以下内容确实可以编译:

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; }
}
4

8 回答 8

42

到目前为止,其他答案存在很多混乱。(虽然 Preston Guillot 的回答非常好,但实际上并没有说明这里发生了什么。)让我试着澄清一下。

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

  1. 实现GetEnumerator与所需模式匹配的公共。
  2. 实施IEnumerable(当然,IEnumerable<T>需要IEnumerable
  3. 是动态的,在这种情况下,我们只需将罐子踢下来并在运行时进行分析。

结果是集合类型必须实际实现一种GetEnumerator方式或另一种方式。提供扩展方法并不能削减它。

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

这种不幸的情况是语言发展和发展的结果。旧版本是为满足时代需求而设计的,而新版本必须建立在此基础上。相反,如果 C# 1.0 有扩展方法和泛型,那么foreach循环可以像 LINQ 一样设计:作为一个简单的句法转换。但事实并非如此,现在我们被遗留在泛型前、扩展前方法设计中。

其次,在其他答案和评论中似乎存在一些错误信息,即关于foreach工作的确切要求。您不需要实施IEnumerable. 有关此通常被误解的功能的更多详细信息,请参阅我关于该主题的文章

第三,对于这种行为是否真的被规范证明是合理的,似乎存在一些问题。这是。规范没有明确指出在这种情况下不考虑扩展方法,这是不幸的。但是,规范非常清楚会发生什么:

编译器首先GetEnumerator. 成员查找算法在 7.3 节中有详细记录,成员查找不考虑扩展方法,只考虑实际成员。扩展方法仅在常规重载决议失败后才考虑,我们还没有得到重载决议。(是的,成员访问考虑了扩展方法,但是成员访问成员查找是不同的操作。)

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

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

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

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

foreach (var row in table)

超过

foreach(var row in table.Rows)

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

于 2013-01-05T14:18:04.087 回答
3

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

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

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

编辑:

从 C# 9.0 开始,GetEnumerator 可以是扩展方法。

于 2013-01-05T03:17:06.427 回答
0

在 foreach 语句中,编译器正在寻找 GetEnumerator 的实例方法。因此类型(此处为 DataTable)必须实现 IEnumerable。它永远不会找到您的扩展方法,因为它是静态的。您必须在 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())
  .....

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

于 2013-01-05T14:55:51.230 回答
0

一些题外话:如果你想做更具可读性的写

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

作为

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

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

    public static IEnumerable<T> GetEnumerator<T>(this DataTable table)
    {
        return table.Rows.Cast<T>();
    }
于 2013-01-05T03:06:52.923 回答
0

您的扩展相当于:

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

GetEnumerator<TDataRow>方法不一样GetEnumerator

这将更好地工作:

    public static IEnumerable<DataRow> GetEnumerator( this DataTable tbl ) {
        foreach (DataRow r in tbl.Rows ) yield return r;
    }
于 2013-01-05T03:36:35.763 回答
0

这将在 C# 9 中实现。提案已签入。您可以在语言功能状态 - C# 9中验证它何时在预览版中可用。

在提案的详细设计部分,它说:

否则,确定类型“X”是否具有适当的 GetEnumerator 扩展方法

using System;
using System.Collections.Generic;

public static class MyExtensions
{
    // Note: ranges aren't intended to work like this. It's just an example.
    public static IEnumerable<int> GetEnumerator(this Range range)
    {
        // .. do validation ..
        for (var i = range.Start.Value; i <= range.End.Value; i++)
        {
            yield return i;
        }
    }
}

public class ExtensionGetEnumerator
{

    public void Method()
    {
        var range = 1..2;
        foreach (var i in range.GetEnumerator())
        {
            Console.WriteLine($"Print with explicit GetEnumerator {i}");
        }

        // The feature is in progress, scheduled for C# 9, the below does not compile yet

        //foreach (var i in range)
        //{
        //    Console.WriteLine($"Print with implicit GetEnumerator {i}");
        //}
    }
}
于 2020-07-22T09:35:11.170 回答
0

当前建议通过扩展将 GetEnumerator 添加到 C# https://github.com/dotnet/csharplang/issues/3194

于 2020-02-17T03:49:09.943 回答
-6

foreach必须实现的对象集合System.Collections.IEnumerableor System.Collections.Generic.IEnumerable<T>

如果您非常渴望启用此功能,那么您可以创建一个包装类,该类实现IEnumerable并具有指向您的指针DataTable。或者,您可以继承DataTable一个新类并实现IEnumerable.

代码可读性通常是个人偏好。我个人觉得您对foreach声明的更改不太可读(但我相信 SO 上有很多人同意您的观点)。

于 2013-01-05T04:35:34.480 回答