为什么不能克隆IEnumerator?

Pau*_*rth 9 .net c# ienumerable

在C#中实现一个基本的Scheme解释器时,我惊恐地发现了以下问题:

IEnumerator没有克隆方法!(或者更确切地说,IEnumerable不能为我提供"可克隆"枚举器).

我想要的是什么:

interface IEnumerator<T>
{
    bool MoveNext();
    T Current { get; }
    void Reset();
    // NEW!
    IEnumerator<T> Clone();
}
Run Code Online (Sandbox Code Playgroud)

我无法想出IEnumerable的实现,它无法提供有效的可克隆IEnumerator(向量,链表等等)所有能够提供IEnumerator的克隆()的简单实现,如上所述...它会比提供Reset()方法更容易!).

缺少Clone方法意味着枚举序列的任何功能/递归习惯用法都不起作用.

这也意味着我无法"无缝地"使IEnumerable的行为像Lisp"列表"(为此你使用car/cdr递归枚举).即唯一的实现"(cdr some IEnumerable)"将是非常低效的.

任何人都可以建议一个现实的,有用的IEnumerable对象的例子,它无法提供有效的"Clone()"方法吗?是否存在"收益"构造的问题?

任何人都可以建议解决方法?

Dan*_*ker 23

逻辑是不可阻挡的!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;
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,该ToEnumerable方法使您可以方便地使用标准C#方式.

回答你的问题:

任何人都可以建议一个现实的,有用的IEnumerable对象的例子,它无法提供有效的"Clone()"方法吗?是否存在"收益"构造的问题?

IEnumerable可以在世界上任何地方获取其数据.这是一个从控制台读取行的示例:

IEnumerable<string> GetConsoleLines()
{
    for (; ;)
        yield return Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)

这有两个问题:首先,Clone函数编写起来并不是特别简单(并且Reset没有意义).其次,序列是无限的 - 这是完全允许的.序列是懒惰的.

另一个例子:

IEnumerable<int> GetIntegers()
{
    for (int n = 0; ; n++)
        yield return n;
}
Run Code Online (Sandbox Code Playgroud)

对于这两个例子,你接受的"解决方法"没什么用处,因为它会耗尽可用内存或永远挂断.但这些都是完整有效的序列例子.

要了解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];       
    }
}
Run Code Online (Sandbox Code Playgroud)

如果在套接字下发送了一些字节数,则不会是无限序列.然而,为它编写克隆将是非常困难的.编译器如何生成IEnumerable实现以自动执行?

一旦创建了克隆,两个实例现在都必须从他们共享的缓冲系统中工作.这是可能的,但实际上并不需要 - 这不是设计使用这些序列的方式.你纯粹在"功能上"对待它们,比如值,递归地应用过滤器,而不是"命令性地"记住序列中的位置.它比低级car/ cdr操纵更清洁.

进一步问题:

我想知道,我需要的最低级别的"原语"是什么,我可能想要在我的Scheme解释器中使用IEnumerable做任何事情都可以在方案中实现而不是作为内置实现.

我认为简短的回答是关注Abelson和Sussman,特别是关于溪流的部分.IEnumerable是一个流,而不是一个列表.它们描述了如何使用特殊版本的map,filter,accumulate等来处理它们.他们还在4.2节中提出了统一列表和流的想法.