할 수 없는 이유 IEnumerator 의 복제?
-
03-07-2019 - |
문제
을 구현하기 위한 기본적인 계획에 통역 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을 위해서만 있으므로, 당신은 자유롭지 않은 외식을 자유롭게 던질 수 있습니다.