逻辑无懈可击!IEnumerable
不支持Clone
,你需要Clone
,所以你不应该使用IEnumerable
.
或者更准确地说,您不应该将它用作在 Scheme 解释器上工作的基本基础。为什么不创建一个简单的不可变链表呢?
public class Link<TValue>
{
private readonly TValue value;
private readonly Link<TValue> next;
public Link(TValue value, Link<TValue> next)
{
this.value = value;
this.next = next;
}
public TValue Value
{
get { return value; }
}
public Link<TValue> Next
{
get { return next; }
}
public IEnumerable<TValue> ToEnumerable()
{
for (Link<TValue> v = this; v != null; v = v.next)
yield return v.value;
}
}
请注意,该ToEnumerable
方法可让您以标准 C# 方式方便地使用。
要回答您的问题:
任何人都可以提出一个现实、有用的 IEnumerable 对象示例,该对象无法提供有效的“Clone()”方法吗?“产量”结构是否存在问题?
IEnumerable 可以在世界任何地方获取其数据。这是一个从控制台读取行的示例:
IEnumerable<string> GetConsoleLines()
{
for (; ;)
yield return Console.ReadLine();
}
这样做有两个问题:首先,Clone
函数编写起来不是特别简单(而且Reset
毫无意义)。其次,序列是无限的——这是完全可以允许的。序列是惰性的。
另一个例子:
IEnumerable<int> GetIntegers()
{
for (int n = 0; ; n++)
yield return n;
}
对于这两个示例,您接受的“解决方法”不会有太大用处,因为它只会耗尽可用内存或永远挂断。但这些都是完全有效的序列示例。
要理解 C# 和 F# 序列,您需要查看 Haskell 中的列表,而不是 Scheme 中的列表。
如果您认为无限的东西是红鲱鱼,那么从套接字读取字节怎么样:
IEnumerable<byte> GetSocketBytes(Socket s)
{
byte[] buffer = new bytes[100];
for (;;)
{
int r = s.Receive(buffer);
if (r == 0)
yield break;
for (int n = 0; n < r; n++)
yield return buffer[n];
}
}
如果有一些字节被发送到套接字,这将不是一个无限的序列。然而,为此编写克隆将非常困难。编译器将如何生成 IEnumerable 实现来自动执行它?
一旦创建了克隆,两个实例现在都必须从它们共享的缓冲区系统中工作。这是可能的,但实际上它不是必需的——这不是这些序列的设计使用方式。您将它们纯粹“功能性地”对待,就像值一样,递归地对它们应用过滤器,而不是“强制性地”记住序列中的位置。它比低级car
/cdr
操作要干净一些。
进一步的问题:
我想知道,我需要的最低级别的“原语”是什么,这样我可能想在我的 Scheme 解释器中对 IEnumerable 做的任何事情都可以在 scheme 中实现,而不是作为内置实现。
我认为简短的答案是查看Abelson 和 Sussman,尤其是有关流的部分。IEnumerable
是一个流,而不是一个列表。它们描述了您如何需要特殊版本的地图、过滤器、累积等来使用它们。他们还在 4.2 节中提出了统一列表和流的想法。