关于 IEnumerable 和 IEnumerator 的问题

7

我使用以下代码使myClass能够使用foreach。但是我对编程还比较陌生,有些难以理解以下代码。我在注释中描述了我的问题。能否提供一些信息将不胜感激。

    public class MyClass : IEnumerable<string> 
    {  
    //1) What is IEnumerator for?
        // Whats the difference between IEnumerator and IEnumerable
    public IEnumerator<string> GetEnumerator()     
    {
             yield return "first";         
             yield return "second";     
    }      
    //2) What is it for?  It just calls above method 
   IEnumerator IEnumerable.GetEnumerator()
         {
             return GetEnumerator(); 
         } 
    }
   //3) Lastly what benefits I have from implementing genetic interface 
   //IEnumerable<string> instead of just IEnumerable

3
我会让 John Skeet 来回答这个问题。 - Muhammad Hasan Khan
4
@Hasan: 我担心他通过拼错他的名字来对你做出某些事情。 - JYelton
7个回答

12

IEnumerator和IEnumerable有什么区别?

Jason的答案很好,但我想补充一下我的思考方式。想象一下你有一个序列:

1, 1, 2, 3, 5, 8, 13, ...

现在想象一下,您有一个箭头指向该序列的某个位置:

1, 1, 2, 3, 5, 8, 13, ...
         ^

“箭头”是一个能够做两件事情的对象。第一,它可以给你指向的东西。第二,它可以让自己指向下一个事物。

IEnumerator 是一个箭头。它有一个属性Current,可以给你它指向的事物。它有一个方法MoveNext(),可以让自己指向下一个事物。

那么如何获得箭头呢?你需要一个箭头工厂。你向工厂请求一个箭头,它会给你一个指向序列中第一个元素的箭头。

IEnumerable 是一个箭头工厂。它有一个方法GetEnumerator,可以给你一个指向序列中第一个元素的箭头。

这种方案的好处是你可以有多个箭头指向同一序列中的不同位置。

实现泛型接口IEnumerable<T>而不是仅使用IEnumerable有什么好处?

假设序列是整数。如果你实现了IEnumerable,那么当你说:

foreach(int x in mysequence)

它实际上会将序列中的int转换为对象,装箱整数,然后立即将对象拆箱回整数,使每个操作都增加了完全不必要的内存分配。如果编译器知道序列是整数,则可以跳过不必要的装箱操作。

假设序列是字符串。如果您实现了 IEnumerable<string>,则可以这样说:

string first = mysequence.First();
如果你不这样做,那么你必须说出来。
string first = (string)mysequence.First();

通过类型转换来告诉编译器变量的数据类型是没必要且容易出错的。更好的做法是利用类型系统,确保变量的数据类型是字符串。


2
箭头概念的比喻很好。值得一提的是,这种模型的一个常见副作用是,修改正在枚举的集合通常会使指向它的所有“箭头”无效。 - LBushkin
关于“GetEnumerator,它给你一个指向序列第一个元素的箭头”的小注释:根据MSDN的说法,它会给你一个指向第一个元素之前某个未定义的东西的箭头。 - jan

6

1) 什么是IEnumeratorIEnumeratorIEnumerable有什么区别?

IEnumerator是一个接口,它表示让您枚举序列的方法。 IEnumeratorIEnumerable之间的区别在于前者代表让您枚举序列的对象的合同,而后者代表可以枚举的序列的对象的合同。

public IEnumerator<string> GetEnumerator() {
         yield return "first";         
         yield return "second";     
}      


IEnumerator IEnumerable.GetEnumerator() {
    return GetEnumerator(); 
} 

2) 这是干什么用的?它只是调用了上面的方法。

前者表示在契约 IEnumerable<string> 上实现了方法 GetEnumerator。后者表示在契约 IEnumerable 上显式实现了方法 GetEnumerator。问题在于两个契约都有一个名为 GetEnumerator 的方法,但返回类型不同,因此一个方法无法同时满足两个契约(任何实现 IEnumerable<T> 的类也必须实现 IEnumerable,因为 IEnumerable<T> : IEnumerable)。后者调用了 IEnumerable<string>.GetEnumerator 的实现,因为这是一个合理的实现,返回一个 IEnumerator,因为 IEnumerator<string> : IEnumerator

3) 最后,实现泛型接口 IEnumerable<string> 相比只实现 IEnumerable 有什么好处?

强类型化。您知道序列 IEnumerable<string> 中的元素都是 String 的实例,而对于 IEnumerable,您并不知道它们是什么类型,可能会尝试将后者的元素强制转换为 String 的实例,但实际上不是这样的。


3

1) IEnumerator是用来做什么的?

IEnumerator是使用MoveNext()和Current成员的真正工作部分。

IEnumerator和IEnumerable有什么区别?

IEnumerable是一个集合的接口,用于表示它具有GetEnumerator()方法。

2) [非泛型GetEnumerator]它是用来做什么的? 它只调用上面的方法

非泛型方法只是为了向后兼容而存在。请注意,通过使用显式实现,它尽可能地被“隐藏”。实现它是因为必须实现,然后就可以忘记它。

3) 最后,实现基因接口IEnumerable相比只实现IEnumerable有哪些好处?

当与 foreach 一起使用时,优点很小,因为foreach将对循环变量进行类型转换。它将让您在 foreach 中使用 var

foreach (var s in myClassInstance)     // needs `IEnumerable<string>` 
foreach (string s in myClassInstance)  // works with  `IEnumerable` as well 

但是使用 IEnumerable<string>,你还可以获得一个类型安全的接口到其他领域,最值得注意的是LINQ:

MyClass mc = new MyClass ();
string s = mc.FirstOrDefault();

3

这是 IEnumerable<T> 的声明:

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

你没有选择,必须实现非泛型 IEnumerable 接口。如果省略 IEnumerable.GetEnumerator() 实现,则会收到编译错误。 这是 .NET 1.x 时代的遗留问题,在那个时候还没有泛型,并且需要过渡期来支持与 .NET 1.x 组件的互操作性。我们无法摆脱它。


0
要理解它们之间的区别,您需要了解它们的用途。实际上,它们代表了迭代器设计模式,这是面向对象软件开发人员使用的设计模式之一。
迭代器模式提供了一种按顺序访问集合元素的方式,而不必知道集合的结构。
在传统的设计模式方法中,迭代器模式具有用于创建迭代器对象的Aggregate接口和用于遍历Aggregate对象的Iterator接口。
现在,IEnumerable充当Aggregate接口,保证返回一个迭代器。迭代器(也称为枚举器)负责按照某些标准生成序列中的下一个元素。IEnumerator是定义此功能的接口。您可以在我的博客文章Iterator Pattern C#中了解更多关于此模式的信息。

0

GetEnumerator自从泛型引入语言之前就存在了,为了不破坏现有的代码,所以默认方法调用通向泛型实现。

使用泛型枚举时,您的类的使用者在迭代时不需要将每个项强制转换为字符串。


0

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