문제

을 구현하기 위한 기본적인 계획에 통역 C#발견했고,내포,다음과 같은 문제는:

IEnumerator 없는 복제하는 방법!(또는 더 정확하게,페을 제공할 수 없습니다 나으로"복제 가능"enumerator).

무엇이겠:

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

나 올 수 없습으로 구현하는 폐쇄되지 않을 것을 공급할 수 있는 효율적으로 복제 가능 IEnumerator(벡터,연결 목록,등등.모든 것을 제공할 수 있는 사소한의 구현 IEnumerator 복제()으로 지정한다.그것은 것 보다 쉽게 제공하 Reset()메소드입니다!!).

의 부재 복제 방법 즉 어떤 기능/recursive 또한 믿음의 경우와 마찬가지로 열거하는 순서로 작동하지 않습니다.

그것은 또한 수단을 나는 할 수 없습니다"원활하게"메이페이의 행동 하는 다음과 같 Lisp"목록"을(예를 사용하는 자동차/지휘관을 열거을 재귀적으로).즉만 implemention 의"(cdr 일부페)"비참하게 될 것이 비효율적이다.

할 수 있는 사람을 제안인,유용한 예이페이는 객체지 않을 제공할 수 있는 효율적인"복제()"방법?그것은 있다는 것에 문제가"수익률"construct?

할 수 있는 사람을 제안 해결 방법?

도움이 되었습니까?

해결책

이 논리는 냉! IEnumerable 지원하지 않는 Clone, 신 Clone, 다,그래서 당신이 사용하지 않아야 IEnumerable.

또는 더 정확하게 당신이 사용하지 않아야 그에 대한 근본적인 기반으로 작업 방식을 통역입니다.왜 사소한 변경할 수 없는 링크 목록을까요?

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

Note ToEnumerable 방법은 당신이 편리한 사용법에는 표준 C#방법입니다.

당신의 질문에 대답:

할 수 있는 사람을 제안 현실적이, 유용하고,예의는 폐쇄 체할 수 없을 것 제공하는 효율적인"복제()"방법?그것이 문제가 있는 것일 는"수익률"construct?

는페이 어디든 갈 수 있습니다 세계에서의 데이터입니다.여기에 예를 읽는 라인에서 콘솔:

IEnumerable<string> GetConsoleLines()
{
    for (; ;)
        yield return Console.ReadLine();
}

두 가지 문제가 있다:첫째, Clone 기능되지 않을 것이 특히 간단하게 쓰(고 Reset 의미가 없).둘째,시퀀스는 무한 완벽하게 허용가능합니다.시퀀스를 재생됩니다.

또 다른 예를 들어:

IEnumerable<int> GetIntegers()
{
    for (int n = 0; ; n++)
        yield return n;
}

모두를 위해 이러한 예는,"해결 과"당신은 허용되지 않을 것은 많이 사용하기 때문에,그것은 그냥 배기 사용 가능한 메모리 또는 끊니다.그러나 이들은 완벽하게 유효한 예습니다.

을 이해하는 C#,F#시퀀스에서 볼 필요가 있어요 목록에서 글꼴을 하지 목록에는 체계입니다.

는 경우에 당신이 생각하이 무한 물건은 빨간 청어,는 방법에 대해 읽기를 바이트에서 소켓:

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

가 있는 경우에는 바이트 수를 아래로 발송되는 소켓이 되지 않을 것입니다 무한한다.그리고 아직 작성 복제품을 위한 것이 매우 어렵습니다.어떻게 컴파일러에서 생성페이 구현하는 자동으로 할?

로 있었는 복제품을 만들,모두 인스턴스는 지금 일해야에서 버퍼 시스템은 그들이 공유합니다.그것은 가능하지만,실제로 그것은 필요 하지 않습니다-이것은 어떻게 이러한 종류의 시퀀스는 설계를 사용할 수 있습니다.당신이 그들을 치료하는 순전히"기능",같은 값 필터를 적용하여 그들을 재귀적으로,보다는 오히려"명령형으로"기억하는 위치에 있습니다.그것은 작은 청소기보다는 낮은 수준 car/cdr 습니다.

추가 질문:

나는 무엇을 궁금해의 가장 낮은 수준 "원시(s)"나는 것이 필요 같은 것을 아무것도 나는 할 수 있습으로 페이 내 방식을 통역 을 구현할 수 있습에서 오히려 scheme 보다 내장.

짧은 대답고 생각하는 것에보 Abelson 및 서스 맨 특히 이 부분에 대한 스트림. IEnumerable 은 스트림에 목록을 사용할 수 없습니다.고 그들이 설명하는 방법 당신은 필요한 특별 버전의 지도,필터를 축적,등등.그들과 함께 작업 할 수 있습니다.그들은 또한 위에 아이디어의 통합 목록과 스트림에서 섹션 4.2.

다른 팁

해결 방법으로 복제를 한 iEenumerator에 대한 확장 방법을 쉽게 만들 수 있습니다. 열거 자에서 목록을 작성하고 요소를 멤버로 사용하십시오.

그러나 열거 자의 스트리밍 기능을 잃어 버릴 것입니다. 그러나 새로운 "클론"은 첫 번째 열거자가 완전히 평가할 수 있기 때문입니다.

원래 열거자를 놓아 줄 수 있다면, 즉. 더 이상 사용하지 않으면 원래 열거자를 취하는 "클론"기능을 구현하여 하나 이상의 열거 자의 소스로 사용할 수 있습니다.

즉, 다음과 같은 것을 만들 수 있습니다.

IEnumerable<String> original = GetOriginalEnumerable();
IEnumerator<String>[] newOnes = original.GetEnumerator().AlmostClone(2);
                                                         ^- extension method
                                                         produce 2
                                                         new enumerators

이들은 열거 된 값을 추적하기 위해 원래 열거 자와 링크 된 목록을 내부적으로 공유 할 수 있습니다.

이것은 다음을 허용합니다.

  • 두 개의 열거자가 앞으로 진행하는 한 무한한 서열 (링크 된 목록은 일단 두 개의 열거자가 특정 지점을 통과하면 GC'ed 될 수 있도록 작성 될 것입니다)
  • 원래 열거 자에서 아직 검색되지 않은 값이 필요한 두 열거 자 중 첫 번째 인 Lazy Enumeration은 그것을 얻고 링크 된 목록에 저장하기 전에 그것을 얻을 수 있습니다.

여기서 문제는 당연히 열거 자 중 하나가 다른 하나보다 훨씬 앞서 나가는 경우 여전히 많은 메모리가 필요하다는 것입니다.

소스 코드는 다음과 같습니다. Subversion을 사용하는 경우 아래 코드가 포함 된 클래스 라이브러리와 함께 Visual Studio 2008 솔루션 파일을 다운로드 할 수 있으며 별도의 단위 테스트 포버가 다운로드 할 수 있습니다.

저장소: http://vkarlsen.serveftp.com:81/svnstackoverflow/so847655
사용자 이름과 암호는 따옴표가없는 '게스트'입니다.

이 코드는 전혀 스레드 안전이 아닙니다.

public static class EnumeratorExtensions
{
    /// <summary>
    /// "Clones" the specified <see cref="IEnumerator{T}"/> by wrapping it inside N new
    /// <see cref="IEnumerator{T}"/> instances, each can be advanced separately.
    /// See remarks for more information.
    /// </summary>
    /// <typeparam name="T">
    /// The type of elements the <paramref name="enumerator"/> produces.
    /// </typeparam>
    /// <param name="enumerator">
    /// The <see cref="IEnumerator{T}"/> to "clone".
    /// </param>
    /// <param name="clones">
    /// The number of "clones" to produce.
    /// </param>
    /// <returns>
    /// An array of "cloned" <see cref="IEnumerator[T}"/> instances.
    /// </returns>
    /// <remarks>
    /// <para>The cloning process works by producing N new <see cref="IEnumerator{T}"/> instances.</para>
    /// <para>Each <see cref="IEnumerator{T}"/> instance can be advanced separately, over the same
    /// items.</para>
    /// <para>The original <paramref name="enumerator"/> will be lazily evaluated on demand.</para>
    /// <para>If one enumerator advances far beyond the others, the items it has produced will be kept
    /// in memory until all cloned enumerators advanced past them, or they are disposed of.</para>
    /// </remarks>
    /// <exception cref="ArgumentNullException">
    /// <para><paramref name="enumerator"/> is <c>null</c>.</para>
    /// </exception>
    /// <exception cref="ArgumentOutOfRangeException">
    /// <para><paramref name="clones"/> is less than 2.</para>
    /// </exception>
    public static IEnumerator<T>[] Clone<T>(this IEnumerator<T> enumerator, Int32 clones)
    {
        #region Parameter Validation

        if (Object.ReferenceEquals(null, enumerator))
            throw new ArgumentNullException("enumerator");
        if (clones < 2)
            throw new ArgumentOutOfRangeException("clones");

        #endregion

        ClonedEnumerator<T>.EnumeratorWrapper wrapper = new ClonedEnumerator<T>.EnumeratorWrapper
        {
            Enumerator = enumerator,
            Clones = clones
        };
        ClonedEnumerator<T>.Node node = new ClonedEnumerator<T>.Node
        {
            Value = enumerator.Current,
            Next = null
        };

        IEnumerator<T>[] result = new IEnumerator<T>[clones];
        for (Int32 index = 0; index < clones; index++)
            result[index] = new ClonedEnumerator<T>(wrapper, node);
        return result;
    }
}

internal class ClonedEnumerator<T> : IEnumerator<T>, IDisposable
{
    public class EnumeratorWrapper
    {
        public Int32 Clones { get; set; }
        public IEnumerator<T> Enumerator { get; set; }
    }

    public class Node
    {
        public T Value { get; set; }
        public Node Next { get; set; }
    }

    private Node _Node;
    private EnumeratorWrapper _Enumerator;

    public ClonedEnumerator(EnumeratorWrapper enumerator, Node firstNode)
    {
        _Enumerator = enumerator;
        _Node = firstNode;
    }

    public void Dispose()
    {
        _Enumerator.Clones--;
        if (_Enumerator.Clones == 0)
        {
            _Enumerator.Enumerator.Dispose();
            _Enumerator.Enumerator = null;
        }
    }

    public T Current
    {
        get
        {
            return _Node.Value;
        }
    }

    Object System.Collections.IEnumerator.Current
    {
        get
        {
            return Current;
        }
    }

    public Boolean MoveNext()
    {
        if (_Node.Next != null)
        {
            _Node = _Node.Next;
            return true;
        }

        if (_Enumerator.Enumerator.MoveNext())
        {
            _Node.Next = new Node
            {
                Value = _Enumerator.Enumerator.Current,
                Next = null
            };
            _Node = _Node.Next;
            return true;
        }

        return false;
    }

    public void Reset()
    {
        throw new NotImplementedException();
    }
}

이것은 반사를 사용하여 새 인스턴스를 작성한 다음 새 인스턴스에서 값을 설정합니다. 또한이 장에서 C# 의이 장이 매우 유용하다는 것을 알았습니다.반복자 블록 구현 세부 사항 : 자동 생성 상태 머신

static void Main()
{
    var counter = new CountingClass();
    var firstIterator = counter.CountingEnumerator();
    Console.WriteLine("First list");
    firstIterator.MoveNext();
    Console.WriteLine(firstIterator.Current);

    Console.WriteLine("First list cloned");
    var secondIterator = EnumeratorCloner.Clone(firstIterator);

    Console.WriteLine("Second list");
    secondIterator.MoveNext();
    Console.WriteLine(secondIterator.Current);
    secondIterator.MoveNext();
    Console.WriteLine(secondIterator.Current);
    secondIterator.MoveNext();
    Console.WriteLine(secondIterator.Current);

    Console.WriteLine("First list");
    firstIterator.MoveNext();
    Console.WriteLine(firstIterator.Current);
    firstIterator.MoveNext();
    Console.WriteLine(firstIterator.Current);
}

public class CountingClass
{
    public IEnumerator<int> CountingEnumerator()
    {
        int i = 1;
        while (true)
        {
            yield return i;
            i++;
        }
    }
}

public static class EnumeratorCloner
{
    public static T Clone<T>(T source) where T : class, IEnumerator
    {
        var sourceType = source.GetType().UnderlyingSystemType;
        var sourceTypeConstructor = sourceType.GetConstructor(new Type[] { typeof(Int32) });
        var newInstance = sourceTypeConstructor.Invoke(new object[] { -2 }) as T;

        var nonPublicFields = source.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
        var publicFields = source.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
        foreach (var field in nonPublicFields)
        {
            var value = field.GetValue(source);
            field.SetValue(newInstance, value);
        }
        foreach (var field in publicFields)
        {
            var value = field.GetValue(source);
            field.SetValue(newInstance, value);
        }
        return newInstance;
    }
}

이 답변은 다음 질문에도 사용되었습니다 반복 상태의 사본을 저장하여 ienumerable 인스턴스를 복제 할 수 있습니까?

이것이 확장 방법으로하지 않는 이유 :

public static IEnumerator<T> Clone(this IEnumerator<T> original)
{
    foreach(var v in original)
        yield return v;
}

이것은 기본적으로 원본을 완전히 평가하지 않고 새 열거자를 생성하고 반환합니다.

편집 : 예, 오해. 바울은 맞습니다. 이것은 ienumerable과 함께 작동합니다.

이것은 도움이 될 수 있습니다. ienumerator에서 dispose () 호출하려면 코드가 필요합니다.

class Program
{
    static void Main(string[] args)
    {
        //var list = MyClass.DequeueAll().ToList();
        //var list2 = MyClass.DequeueAll().ToList();

        var clonable = MyClass.DequeueAll().ToClonable();


        var list = clonable.Clone().ToList();
        var list2 = clonable.Clone()ToList();
        var list3 = clonable.Clone()ToList();
    }
}

class MyClass
{
    static Queue<string> list = new Queue<string>();

    static MyClass()
    {
        list.Enqueue("one");
        list.Enqueue("two");
        list.Enqueue("three");
        list.Enqueue("four");
        list.Enqueue("five");
    }

    public static IEnumerable<string> DequeueAll()
    {
        while (list.Count > 0)
            yield return list.Dequeue();
    }
}

static class Extensions
{
    public static IClonableEnumerable<T> ToClonable<T>(this IEnumerable<T> e)
    {
        return new ClonableEnumerable<T>(e);
    }
}

class ClonableEnumerable<T> : IClonableEnumerable<T>
{
    List<T> items = new List<T>();
    IEnumerator<T> underlying;

    public ClonableEnumerable(IEnumerable<T> underlying)
    {
        this.underlying = underlying.GetEnumerator();
    }

    public IEnumerator<T> GetEnumerator()
    {
        return new ClonableEnumerator<T>(this);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    private object GetPosition(int position)
    {
        if (HasPosition(position))
            return items[position];

        throw new IndexOutOfRangeException();
    }

    private bool HasPosition(int position)
    {
        lock (this)
        {
            while (items.Count <= position)
            {
                if (underlying.MoveNext())
                {
                    items.Add(underlying.Current);
                }
                else
                {
                    return false;
                }
            }
        }

        return true;
    }

    public IClonableEnumerable<T> Clone()
    {
        return this;
    }


    class ClonableEnumerator<T> : IEnumerator<T>
    {
        ClonableEnumerable<T> enumerable;
        int position = -1;

        public ClonableEnumerator(ClonableEnumerable<T> enumerable)
        {
            this.enumerable = enumerable;
        }

        public T Current
        {
            get
            {
                if (position < 0)
                    throw new Exception();
                return (T)enumerable.GetPosition(position);
            }
        }

        public void Dispose()
        {
        }

        object IEnumerator.Current
        {
            get { return this.Current; }
        }

        public bool MoveNext()
        {
            if(enumerable.HasPosition(position + 1))
            {
                position++;
                return true;
            }
            return false;
        }

        public void Reset()
        {
            position = -1;
        }
    }


}

interface IClonableEnumerable<T> : IEnumerable<T>
{
    IClonableEnumerable<T> Clone();
}

"클로닝 가능한"열거 자의 목적은 주로 반복 위치를 저장하고 나중에 돌아올 수 있습니다. 즉, 반복 컨테이너는 단순한 것보다 더 풍부한 인터페이스를 제공해야합니다. IEnumerable. 그것은 실제로 사이에 무언가입니다 IEnumerable 그리고 IList. 작업 IList 정수 색인을 열거 자로 사용하거나 목록 및 현재 위치에 대한 참조를 사용하여 간단한 불변의 랩핑 클래스를 만들 수 있습니다.

컨테이너가 무작위 액세스를 지원하지 않고 반복적으로 만 반복 할 수있는 경우 (1 차 방향 링크 목록과 같은), 적어도 다음 요소를 얻을 수있는 능력을 제공해야합니다. 반복자를 잡을 수 있습니다. 따라서 인터페이스는 다음과 같이 보일 수 있습니다.

interface IIterable<T>
{
    IIterator<T> GetIterator(); // returns an iterator positioned at start
    IIterator<T> GetNext(IIterator<T> prev); // returns an iterator positioned at the next element from the given one
}

interface IIterator<T>
{
    T Current { get; }
    IEnumerable<T> AllRest { get; }
}

반복자는 다음과 같습니다 불변, 그것은 "앞으로 움직일 수 없다"는데, 우리는 반복적 인 컨테이너에게 다음 위치를 가리키는 새로운 반복자를 제공하도록 요청할 수 있습니다. 그 이점은 예를 들어 반복자 스택이 있고 필요할 때 이전에 저장된 위치로 돌아가는 등 필요한만큼 반복자를 보관할 수 있다는 것입니다. 정수 인덱스와 마찬가지로 변수에 할당하여 나중에 사용하기 위해 현재 위치를 저장할 수 있습니다.

그만큼 AllRest 속성은 표준 언어 반복 기능을 사용하여 주어진 위치에서 컨테이너 끝까지 반복 해야하는 경우 유용 할 수 있습니다. foraech 또는 linq. 반복자 위치를 변경하지 않습니다 (반복자는 불변). 구현은 반복적으로 할 수 있습니다 GetNext 그리고 yleid return.

그만큼 GetNext 메소드는 실제로 반복자 자체의 일부가 될 수 있습니다.

interface IIterable<T>
{
    IIterator<T> GetIterator(); // returns an iterator positioned at start
}

interface IIterator<T>
{
    T Current { get; }
    IIterator<T> GetNext { get; } // returns an iterator positioned at the next element from the given one
    IEnumerable<T> AllRest { get; }
}

이것은 거의 동일합니다. 다음 상태를 결정하는 논리는 컨테이너 구현에서 반복자 구현으로 이동됩니다. 반복자는 여전히 있습니다 불변. 당신은 "앞으로 움직일 수 없다"고, 다음 요소를 가리키면서 다른 것을 얻을 수 있습니다.

이미 새 열거자를 만들 수있는 방법이 있습니다. 첫 번째를 만든 것과 같은 방식으로 ienumerable.getenumerator. 왜 같은 일을하기 위해 다른 메커니즘이 필요한지 잘 모르겠습니다.

그리고의 정신으로 건조 원리, 나는 당신이 새로운 ienumerator 인스턴스를 만드는 데 대한 책임이 당신의 열거 가능한 클래스와 열거 자 클래스 모두에서 복제되기를 원하는 이유에 대해 궁금합니다. 열거자가 필요한 것 이상의 추가 상태를 유지하도록 강요 할 것입니다.

예를 들어, 링크 된 목록에 대한 열거자를 상상해보십시오. ienumerable의 기본 구현을 위해 해당 클래스는 현재 노드에 대한 참조 만 유지하면됩니다. 그러나 클론을 지원하려면 목록의 헤드에 대한 참조를 유지해야합니다. 그렇지 않으면 사용하지 않습니다. 소스 (ienumerable)로 가서 다른 열거자를 얻을 수있을 때 왜 그 추가 상태를 열거 자에 추가하겠습니까?

테스트 해야하는 코드 경로 수를 두 배로 늘겠습니까? 객체를 제조하는 새로운 방법을 만들 때마다 복잡성을 추가합니다.

* 리셋을 구현하면 헤드 포인터도 필요하지만 문서에 따르면, 재설정은 COM Interop을 위해서만 있으므로, 당신은 자유롭지 않은 외식을 자유롭게 던질 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top