何时调用IEnumerable.GetEnumerator而不是IEnumerable<T>.GetEnumerator?

9

看一下这段代码:

public class myWords : IEnumerable<string>
{
    string[] f = "I love you".Split(new string[]{"lo"},StringSplitOptions.RemoveEmptyEntries);

    public IEnumerator<string> GetEnumerator()
    {
        return f.Select(s => s + "2").GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return f.Select(s => s + "3").GetEnumerator();
    }
}

运行中:

 myWords m = new myWords();
 foreach (var s in m)
 {
     Console.WriteLine(s);
 }

收益率

I 2
ve you2    // notice "2", so the generic Ienumerator has executed.

我了解非泛型的 IEnumerator 版本是为了兼容性。
问题:
  1. 在什么情况下会调用非泛型版本?
  2. 如何强制我的代码使用非泛型的 IEnumerator

答案:
1. 当需要与旧代码或第三方库进行交互时,可能会调用非泛型版本。 2. 若要强制使用非泛型的 IEnumerator,可以将返回类型设置为 IEnumerator 而不是 IEnumerator<T>
3个回答

7

当代码将类强制转换为非泛型接口时,非泛型IEnumerator将被执行:

((IEnumerable)myWords).GetEnumerator(); // this calls the non-generic one

如果您将类传递给需要非泛型IEnumerator的遗留函数,则此内容大多相关。

因此,如果您有一些包含函数的库,并将您的类传递给此函数,则它将使用非泛型IEnumerator。

DoSomeStuffWithAnIEnumerable(IEnumerable x)
{
   var foo = x.GetEnumerator();

   // or, as stackx said, an example with foreach:
   foreach (var foo2 in x)
   Console.WriteLine(foo2);
}


DoSomeStuffWithAnIEnumerable(new myWords());

请注意,仅仅使用通用的IEnumerator实现非通用版本是完全有效的:


public class myWords : IEnumerable<string>
{
    ....    
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

那样你就可以确保它们具有相同的效果。

1
或者,举个使用 foreach 的例子:foreach (object word in (IEnumerable)myWords) … - stakx - no longer contributing

4
其他回答有点偏离了重点。
如果正在foreach的内容(编译时)类型具有一个名称为GetEnumerator的公共非泛型非静态方法且不带参数,那么接口根本不重要。(此方法的返回类型可以是任何泛型或非泛型: 接口不会起作用。)
因此,第一个方法被调用的原因是这是一个公共方法。
您可以更改它:
public class myWords : IEnumerable<string>
{
    string[] f = "I love you".Split(new string[]{"lo"},StringSplitOptions.RemoveEmptyEntries);

    IEnumerator<string> IEnumerable<string>.GetEnumerator()
    {
        return f.Select(s => s + "2").GetEnumerator();
    }

    public IEnumerator GetEnumerator()
    {
        return f.Select(s => s + "3").GetEnumerator();
    }
}

为了证明接口不是必需的,请尝试以下操作:

public class myWords // no interfaces!
{
    string[] f = "I love you".Split(new string[]{"lo"},StringSplitOptions.RemoveEmptyEntries);

    public IEnumerator GetEnumerator()
    {
        return f.Select(s => s + "3").GetEnumerator();
    }
}

然而,实现 IEnumerable<> 是明智的。这样,您的类型可以与 Linq(IEnumerable<> 上的扩展方法)一起使用,并且可以用作其他仅需要 IEnumerable<> 的方法的参数。
另外,最好让返回非泛型 IEnumerator 的方法(或显式接口实现)直接调用返回 IEnumerator<> 的方法(或显式接口实现)。使两个方法返回不同的序列会令人困惑(但对于解答关于操作方式的问题很有用)。

如果你想要证明接口是不必要的,我建议你实现一个结构体,它有一个 Current 属性和 MoveNext 方法,但不实现任何接口,并且让 myWordsGetEnumerator 方法返回该类型而不是 IEnumerable - supercat
好的,“而不是IEnumerator”是你的意思,我猜。我想说的是,C#编译器将根据我指定的条件选择public方法,因此接口是否存在以及接口映射到什么并不重要。我并没有说foreach会按预期进行。我即将尝试您的建议。 - Jeppe Stig Nielsen
@supercat 我写的有效。这是:`class DoMe { public BadE GetEnumerator() { return default(BadE); } } struct BadE { ushort state; public bool MoveNext() { ++state; return true; } public string Current { get { return arr[state % 4]; } } static readonly string[] arr = { "a", "b", "c", "d", }; }` 创建一个新的 `DoMe` 并使用 `foreach` 循环它是有效的。我展示了什么? - Jeppe Stig Nielsen
在注释中进行格式化很难看,但那似乎是我所想的。在你的回答中,输入 myWords 具有返回 IEnumeratorGetEnumerator 方法; 我认为这很有帮助,因为它不仅显示了具有 GetEnumerator 方法的东西不必实现接口,而且枚举器本身也不必实现接口。 - supercat
@supercat 好的,我明白你的观点。请注意,如果您有 public void GetEnumerator() { },并且另外显式实现了 IEnumerable<>,C# 编译器仍然会选择公共方法。返回类型 void 不适用,因此它会给出编译时错误。但它仍然使用公共方法,并忽略类型实现的任何接口。 - Jeppe Stig Nielsen

4

IEnumerable的非泛型版本是通过显式接口实现来实现的。这意味着您只能通过将其转换为接口来调用显式实现的函数。

IEnumerable之所以被显式实现,是因为方法签名相同,除了返回类型不同。

myWords显式转换为IEnumerable允许您像这样调用非泛型版本:(IEnumerable)myWords

C#指南解释了这是如何工作的:显式接口实现


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接