C# 实现不可变枚举器

C# 实现不可变枚举器,c#,ienumerable,immutability,ienumerator,C#,Ienumerable,Immutability,Ienumerator,考虑不可变泛型枚举器的以下可能接口: interface IImmutableEnumerator<T> { (bool Succesful, IImmutableEnumerator<T> NewEnumerator) MoveNext(); T Current { get; } } 接口IImmutableEnumerator { (bool successful,IImmutableEnumerator NewEnumerator)MoveNext

考虑不可变泛型枚举器的以下可能接口:

interface IImmutableEnumerator<T>
{
    (bool Succesful, IImmutableEnumerator<T> NewEnumerator) MoveNext();
    T Current { get; }
}
接口IImmutableEnumerator
{
(bool successful,IImmutableEnumerator NewEnumerator)MoveNext();
T当前{get;}
}
您将如何在c#中以合理的性能方式实现这一点?我有点不知所措,因为.NET中的
IEnumerator
基础结构天生是可变的,我看不到解决它的方法

一个简单的实现是在每个
MoveNext()
上创建一个新的枚举数,并使用
current.Skip(1).GetEnumerator()
传递一个新的内部可变枚举数,但效率非常低

我正在实现一个解析器,它需要能够向前看;使用一个不可变的枚举器会使事情变得更干净、更容易理解,所以我很好奇是否有一种简单的方法可以做到这一点,而我可能会错过


输入是一个
IEnumerable
,我无法更改它。当然,我总是可以使用
ToList()
来具体化可枚举性(使用
IList
,向前看是微不足道的),但是数据可能非常大,如果可能的话,我想避免它。

您可以通过使用单链接列表来实现适合此特定场景的伪不变性。它允许无限前瞻(仅受堆大小的限制),而不允许查看以前处理的节点(除非您碰巧存储了对以前处理的节点的引用,而您不应该这样做)

此解决方案解决了所述的需求(除了不符合您的确切接口,其所有功能仍然完整)

这种链表的用法可能如下所示:

IEnumerable<int> numbersFromZeroToNine = Enumerable.Range(0, 10);

using (IEnumerator<int> enumerator = numbersFromZeroToNine.GetEnumerator())
{
    var node = LazySinglyLinkedListNode<int>.CreateListHead(enumerator);

    while (node != null)
    {
        Console.WriteLine($"Current value: {node.Value}.");

        if (node.Next != null)
        {
            // Single-element look-ahead. Technically you could do node.Next.Next...Next.
            // You can also nest another while loop here, and look ahead as much as needed.
            Console.WriteLine($"Next value: {node.Next.Value}.");
        }
        else
        {
            Console.WriteLine("End of collection reached. There is no next value.");
        }

        node = node.Next;

        // At this point the object which used to be referenced by the "node" local
        // becomes eligible for collection, preventing unbounded memory growth.
    }
}
实施情况如下:

sealed class LazySinglyLinkedListNode<T>
{
    public static LazySinglyLinkedListNode<T> CreateListHead(IEnumerator<T> enumerator)
    {
        return enumerator.MoveNext() ? new LazySinglyLinkedListNode<T>(enumerator) : null;
    }

    public T Value { get; }

    private IEnumerator<T> Enumerator;
    private LazySinglyLinkedListNode<T> _next;

    public LazySinglyLinkedListNode<T> Next
    {
        get
        {
            if (_next == null && Enumerator != null)
            {
                if (Enumerator.MoveNext())
                {
                    _next = new LazySinglyLinkedListNode<T>(Enumerator);
                }
                else
                {
                    Enumerator = null; // We've reached the end.
                }
            }

            return _next;
        }
    }

    private LazySinglyLinkedListNode(IEnumerator<T> enumerator)
    {
        Value = enumerator.Current;
        Enumerator = enumerator;
    }
}
密封类LazySinglyLinkedListNode
{
公共静态LazySinglyLinkedListNode CreateListHead(IEnumerator枚举器)
{
返回枚举器.MoveNext()?新LazySinglyLinkedListNode(枚举器):null;
}
公共T值{get;}
私有IEnumerator枚举器;
私有LazySinglyLinkedListNode\u next;
公共LazyinglyLinkedListNode下一步
{
得到
{
if(_next==null&&Enumerator!=null)
{
if(枚举数.MoveNext())
{
_next=新LazySinglyLinkedListNode(枚举器);
}
其他的
{
Enumerator=null;//我们已经到了末尾。
}
}
返回下一步;
}
}
私有LazySinglyLinkedListNode(IEnumerator枚举器)
{
值=枚举数。当前值;
枚举数=枚举数;
}
}
这里需要注意的一点是,源集合只被惰性地枚举一次,每个节点的生存期最多调用一次
MoveNext
,而不管您访问
Next
多少次

使用双链表将允许向后查看,但会导致无限内存增长,并需要定期修剪,这并非小事。只要不将节点引用存储在主循环之外,单链表就可以避免此问题。在上面的示例中,您可以用无限生成整数的
IEnumerable
生成器替换
numbersFromZeroToNine
,循环将永远运行而不会耗尽内存。

就是这样:

public class ImmutableEnumerator<T> : IImmutableEnumerator<T>, IDisposable
{
    public static (bool Succesful, IImmutableEnumerator<T> NewEnumerator) Create(IEnumerable<T> source)
    {
        var enumerator = source.GetEnumerator();
        var successful = enumerator.MoveNext();
        return (successful, new ImmutableEnumerator<T>(successful, enumerator));
    }
    private IEnumerator<T> _enumerator;
    private (bool Succesful, IImmutableEnumerator<T> NewEnumerator) _runOnce = (false, null);
    private ImmutableEnumerator(bool successful, IEnumerator<T> enumerator)
    {
        _enumerator = enumerator;
        this.Current = successful ? _enumerator.Current : default(T);
        if (!successful)
        {
            _enumerator.Dispose();
        }
    }
    public (bool Succesful, IImmutableEnumerator<T> NewEnumerator) MoveNext()
    {
        if (_runOnce.NewEnumerator == null)
        {
            var successful = _enumerator.MoveNext();
            _runOnce = (successful, new ImmutableEnumerator<T>(successful, _enumerator));
        }
        return _runOnce;
    }
    public T Current { get; private set; }
    public void Dispose()
    {
        _enumerator.Dispose();
    }
}
公共类ImmutableEnumerator:IImmutableEnumerator,IDisposable
{
公共静态(布尔成功,IImmutableEnumerator NewEnumerator)创建(IEnumerable源)
{
var枚举器=source.GetEnumerator();
var successful=enumerator.MoveNext();
返回(成功,新的ImmutableEnumerator(成功,enumerator));
}
私有IEnumerator_枚举器;
private(bool successful,IImmutableEnumerator NewEnumerator)\u runOnce=(false,null);
私有ImmutableEnumerator(布尔成功,IEnumerator枚举器)
{
_枚举数=枚举数;
this.Current=成功?\u枚举器.Current:默认值(T);
如果(!成功)
{
_枚举数。Dispose();
}
}
public(bool successful,IImmutableEnumerator NewEnumerator)MoveNext()
{
if(_runOnce.NewEnumerator==null)
{
var successful=_枚举数.MoveNext();
_runOnce=(成功,新的ImmutableEnumerator(成功,enumerator));
}
回程跳动;
}
公共T当前{get;私有集;}
公共空间处置()
{
_枚举数。Dispose();
}
}
我的测试代码很成功:

var xs = new[] { 1, 2, 3 };

var ie = ImmutableEnumerator<int>.Create(xs);
if (ie.Succesful)
{
    Console.WriteLine(ie.NewEnumerator.Current);
    var ie1 = ie.NewEnumerator.MoveNext();
    if (ie1.Succesful)
    {
        Console.WriteLine(ie1.NewEnumerator.Current);
        var ie2 = ie1.NewEnumerator.MoveNext();
        if (ie2.Succesful)
        {
            Console.WriteLine(ie2.NewEnumerator.Current);
            var ie3 = ie2.NewEnumerator.MoveNext();
            if (ie3.Succesful)
            {
                Console.WriteLine(ie3.NewEnumerator.Current);
                var ie4 = ie3.NewEnumerator.MoveNext();
            }
        }
    }
}
var xs=new[]{1,2,3};
var ie=ImmutableEnumerator.Create(xs);
如果(成功)
{
Console.WriteLine(即NewEnumerator.Current);
var ie1=ie.NewEnumerator.MoveNext();
如果(ie1.成功)
{
Console.WriteLine(ie1.NewEnumerator.Current);
var ie2=ie1.NewEnumerator.MoveNext();
如果(ie2.成功)
{
Console.WriteLine(ie2.NewEnumerator.Current);
var ie3=ie2.NewEnumerator.MoveNext();
如果(ie3.成功)
{
Console.WriteLine(ie3.NewEnumerator.Current);
var ie4=ie3.NewEnumerator.MoveNext();
}
}
}
}
这将产生:

1 2 3 1. 2. 3. 它是不变的,而且是有效的


以下是根据注释中的请求使用
Lazy
的版本:

public class ImmutableEnumerator<T> : IImmutableEnumerator<T>, IDisposable
{
    public static (bool Succesful, IImmutableEnumerator<T> NewEnumerator) Create(IEnumerable<T> source)
    {
        var enumerator = source.GetEnumerator();
        var successful = enumerator.MoveNext();
        return (successful, new ImmutableEnumerator<T>(successful, enumerator));
    }
    private IEnumerator<T> _enumerator;
    private Lazy<(bool, IImmutableEnumerator<T>)> _runOnce;
    private ImmutableEnumerator(bool successful, IEnumerator<T> enumerator)
    {
        _enumerator = enumerator;
        this.Current = successful ? _enumerator.Current : default(T);
        if (!successful)
        {
            _enumerator.Dispose();
        }
        _runOnce = new Lazy<(bool, IImmutableEnumerator<T>)>(() =>
        {
            var s = _enumerator.MoveNext();
            return (s, new ImmutableEnumerator<T>(s, _enumerator));
        });
    }
    public (bool Succesful, IImmutableEnumerator<T> NewEnumerator) MoveNext()
    {
        return _runOnce.Value;
    }
    public T Current { get; private set; }
    public void Dispose()
    {
        _enumerator.Dispose();
    }
}
公共类ImmutableEnumerator:IImmutableEnumerator,IDisposable
{
公共静态(布尔成功,IImmutableEnumerator NewEnumerator)创建(IEnumerable源)
{
var枚举器=source.GetEnumerator();
var successful=enumerator.MoveNext();
返回(成功,新的ImmutableEnumerator(成功,enumerator));
}
私有IEnumerator_枚举器;
私人偷懒;
私人Imm
public class ImmutableEnumerator<T> : IImmutableEnumerator<T>, IDisposable
{
    public static (bool Succesful, IImmutableEnumerator<T> NewEnumerator) Create(IEnumerable<T> source)
    {
        var enumerator = source.GetEnumerator();
        var successful = enumerator.MoveNext();
        return (successful, new ImmutableEnumerator<T>(successful, enumerator));
    }
    private IEnumerator<T> _enumerator;
    private Lazy<(bool, IImmutableEnumerator<T>)> _runOnce;
    private ImmutableEnumerator(bool successful, IEnumerator<T> enumerator)
    {
        _enumerator = enumerator;
        this.Current = successful ? _enumerator.Current : default(T);
        if (!successful)
        {
            _enumerator.Dispose();
        }
        _runOnce = new Lazy<(bool, IImmutableEnumerator<T>)>(() =>
        {
            var s = _enumerator.MoveNext();
            return (s, new ImmutableEnumerator<T>(s, _enumerator));
        });
    }
    public (bool Succesful, IImmutableEnumerator<T> NewEnumerator) MoveNext()
    {
        return _runOnce.Value;
    }
    public T Current { get; private set; }
    public void Dispose()
    {
        _enumerator.Dispose();
    }
}