c# - 为什么不能克隆 IEnumerator?

标签 c# .net ienumerable

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

IEnumerator 没有克隆方法! (或者更准确地说,IEnumerable 无法为我提供“可克隆”枚举器)。

我想要什么:

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

我无法想出一个 IEnumerable 的实现,它不能提供一个有效的可克隆的 IEnumerator(向量、链表等。所有这些都能够提供上面指定的 IEnumerator 的 Clone() 的简单实现。 .无论如何,这比提供 Reset() 方法更容易!

缺少 Clone 方法意味着枚举序列的任何函数式/递归惯用语都不起作用。

这也意味着我不能“无缝地”使 IEnumerable 的行为像 Lisp“列表”(您使用 car/cdr 递归枚举)。即,“(cdr some IEnumerable)”的唯一实现将非常低效。

任何人都可以提出一个现实的、有用的、无法提供有效的“Clone()”方法的 IEnumerable 对象示例吗?是不是“yield”结构有问题?

谁能提出解决方法?

最佳答案

逻辑无懈可击! 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# 方式方便地使用。

回答你的问题:

Can anyone suggest a realistic, useful, example of an IEnumerable object that wouldn't be able to provide an efficient "Clone()" method? Is it that there'd be a problem with the "yield" construct?

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];       
    }
}

如果有一定数量的字节被发送到套接字,这将不是一个无限序列。然而,为此编写 Clone 将非常困难。编译器将如何生成 IEnumerable 实现以自动执行此操作?

一旦创建了克隆,两个实例现在都必须从它们共享的缓冲系统中工作。这是可能的,但实际上并不需要——这不是设计使用这些类型序列的方式。你纯粹“功能性”地对待它们,就像值一样,递归地对它们应用过滤器,而不是“强制性地”记住序列中的位置。它比低级 car/cdr 操作更干净。

进一步的问题:

I wonder, what's the lowest level "primitive(s)" I would need such that anything I might want to do with an IEnumerable in my Scheme interpreter could be implemented in scheme rather than as a builtin.

我认为简短的回答是查看 Abelson and Sussman特别是 the part about streams . IEnumerable 是一个流,而不是一个列表。他们还描述了您如何需要特殊版本的 map、filter、accumulate 等来使用它们。他们还在 4.2 节中提出了统一列表和流的想法。

关于c# - 为什么不能克隆 IEnumerator?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/807991/

相关文章:

c# - WaitHandle.WaitAny 和信号量类

c# - Entity Framework 在没有标识列的表上不起作用

.net - .NET 如何以及何时实际编译代码?

c# - 使用回车键在 Windows 窗体中导航

c# - 有没有办法枚举一个类中的所有静态函数?

c# - 在 IEnumerable 上使用扩展方法的 Foreach

c# - 您可以通过在 Controller 中对同步代码使用 Task.Run 来提高吞吐量吗?

c# - MVVM Light 和 set 数据模型字段

c# - Gridview 中的多列排序?

c# - 当我不在 for 循环中使用 ToList 方法时,为什么会失败?