IEnumerable 如何在后台运行
我正在徘徊IEnumerable
接口的更深入的function。
基本上,它作为执行中的中间步骤。 例如,如果你写:
IEnumerable temp = new int[]{1,2,3}.Select(x => 2*x);
在使用temp进行某些操作(例如List list = temp.ToList()
)之前,不会计算(枚举) Select
函数的结果。
但是,令我困惑的是,因为IEnumerable
是一个接口,所以根据定义它不能被实例化。 那么,实际项目(在示例2*x
项目中)所在的集合是什么?
而且,如果我们要编写IEnumerable temp = Enumerable.Repeat(1, 10);
,什么是存储1s的底层集合(数组,列表,其他)?
我似乎无法找到关于此接口及其function的实际实现的详尽(更深入)解释(例如,如果存在底层集合, yield
关键字如何工作)。
基本上,我要求的是对IEnumerable
的function进行更详细的解释。
并非所有实现IEnumerable
对象都以某种方式延迟执行。 接口的API 可以延迟执行,但它不需要它。 同样的实现不以任何方式推迟执行。
那么,实际项目(在示例2 * x项目中)所在的集合是什么?
空无一人。 每当请求下一个值时,它会根据需要计算一个值,将其提供给调用者,然后忘记该值。 它不会将其存储在任何其他地方。
而且,如果我们要编写
IEnumerable
,什么是存储1s的底层集合(数组,列表,其他)?temp = Enumerable.Repeat(1, 10);
没有一个。 当你要求下一个值时 ,它会立即计算每个新值,之后就不会记住它。 它只存储足够的信息以便能够计算下一个值,这意味着它只需要存储元素和剩余的值来产生。
虽然实际的.NET实现将使用更简洁的方法来创建这样的类型,但创建一个推迟执行的枚举并不是特别困难。 即使是漫长的路,这样做也比困难更乏味。 您只需计算迭代器的MoveNext
方法中的下一个值。 在你问的例子中, Repeat
,这很容易,因为你只需要计算是否有另一个值,而不是它是什么:
public class Repeater : IEnumerator { private int count; private T element; public Repeater(T element, int count) { this.element = element; this.count = count; } public T Current { get { return element; } } object IEnumerator.Current { get { return Current; } } public void Dispose() { } public bool MoveNext() { if (count > 0) { count--; return true; } else return false; } public void Reset() { throw new NotSupportedException(); } }
(我省略了一个只返回此类型的新实例的IEnumerable
类型,或者一个静态Repeat
方法,它创建了一个可枚举的新实例。没有什么特别有趣的东西可以看到。)
一个稍微有趣的例子就像Count
:
public class Counter : IEnumerator { private int remaining; public Counter(int start, int count) { Current = start; this.remaining = count; } public int Current { get; private set; } object IEnumerator.Current { get { return Current; } } public void Dispose() { } public bool MoveNext() { if (remaining > 0) { remaining--; Current++; return true; } else return false; } public void Reset() { throw new NotSupportedException(); } }
在这里,我们不仅计算我们是否有另一个值,而是每次为我们请求新值时,下一个值是什么。
实施无关紧要。 所有这些(LINQ)方法都返回IEnumerable
,接口成员是您可以访问的唯一成员,这应该足以使用它们。
但是,如果您真的必须知道,可以在http://sourceof.net上找到实际的实现。
- Enumerable.cs
但是,对于某些方法,您将无法找到显式类声明,因为其中一些使用yield return
,这意味着编译期间编译器会生成正确的类(带状态机)。 例如Enumerable.Repeat
以这种方式实现:
public static IEnumerable Range(int start, int count) { long max = ((long)start) + count - 1; if (count < 0 || max > Int32.MaxValue) throw Error.ArgumentOutOfRange("count"); return RangeIterator(start, count); } static IEnumerable RangeIterator(int start, int count) { for (int i = 0; i < count; i++) yield return start + i; }
您可以在MSDN上阅读更多相关信息: 迭代器(C#和Visual Basic)
那么,实际项目(在示例2 * x项目中)所在的集合是什么?
它不在任何地方。 迭代时会有代码“按需”生成单个项目,但不会预先计算2*x
数字。 除非您调用ToList
或ToArray
,否则它们也不会存储在任何位置。
而且,如果我们要编写IEnumerable temp = Enumerable.Repeat(1,10);,那么存储1s的底层集合(数组,列表,其他东西)是什么?
同样的图片在这里: IEnumerable
的返回实现不是公共的,它根据需要返回其项目,而不将它们存储在任何地方。
C#编译器提供了一种实现IEnumerable
的便捷方式,而无需为其定义类。 您所需要的只是将方法返回类型声明为IEnumerable
,并根据需要使用yield return
来提供值。
- 如何在Entry.state == EntityState.Added的位置设置自定义validation
- 将DisplayTemplate设置为WebGrid列
- 为什么Urlmon.dll中的FindMimeFromData函数会为许多文件类型返回MIME类型“application / octet-stream”?
- 可以用LINQ语句替换所有’for’循环吗?
- 获取.NET中的文件类型
- 警告:“…覆盖Object.Equals(对象o)但不覆盖Object.GetHashCode()”
- 带有Microsoft Edge驱动程序的Selenium永远不会完成初始化
- 使用C#将属性值复制到另一个对象
- Lambda变量范围