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

时间:2013-01-05 03:01:30

标签: c# foreach extension-methods

我正在尝试使一些代码更具可读性。例如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;
    ...
}

在您说这是因为IEnumeratorIEnumerator<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; }
}

8 个答案:

答案 0 :(得分:40)

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

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

  1. 实施符合所需模式的公开GetEnumerator
  2. 实施IEnumerable(当然,IEnumerable<T>需要IEnumerable
  3. 保持动态,在这种情况下,我们只需将罐子放在路上并在运行时进行分析。
  4. 结果是集合类型必须以某种方式实际实现 GetEnumerator。提供扩展方法不会削减它。

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

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

    第二,其他答案和评论中似乎有一些错误信息,说明使foreach工作的确切要求。您无需实施IEnumerable。有关此常被误解的功能的更多详细信息,请参阅my article on the subject

    第三,似乎有一些问题是这个行为是否实际上是由规范证明的。它是。规范没有明确地指出在这种情况下不考虑扩展方法,这是不幸的。但是,规范对发生的事情非常清楚:

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

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

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

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

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

    令人信服的好处
    foreach (var row in table)
    

    foreach(var row in table.Rows)
    

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

答案 1 :(得分:4)

测试类中的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 实例方法。

答案 2 :(得分:0)

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

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

答案 3 :(得分: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;
    }

答案 4 :(得分: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()

答案 5 :(得分:0)

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

答案 6 :(得分:0)

这在C#9中是可能的。proposal已经签入。您可以在Language Feature Status - 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}");
        //}
    }
}

答案 7 :(得分:-6)

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

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

代码可读性通常是个人偏好。我个人认为您对foreach声明的更改不太可读(但我相信很多人都同意你的观点)。