سؤال

في تنفيذ المخطط الأساسي مترجم في C# اكتشفت ، إلى بلدي الرعب المشكلة التالية:

IEnumerator لا يجب استنساخ الطريقة!(أو بتعبير أدق ، IEnumerable لا يمكن أن توفر لي مع "كلونيابل" العداد).

ما أود:

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

أنا لا يمكن أن تأتي مع تنفيذ IEnumerable التي لن تكون قادرة على توفير كفاءة كلونيابل IEnumerator (ناقلات, القوائم المتصلة ، إلخ.كل ما من شأنه أن تكون قادرة على توفير تافهة تنفيذ IEnumerator هو استنساخ() كما هو محدد أعلاه...سيكون أسهل من توفير إعادة تعيين (طريقة) على أي حال!).

غياب استنساخ الأسلوب يعني أن أي الوظيفية/العودية لغة من تعداد أكثر من تسلسل لن تعمل.

وهذا يعني أيضا لا أستطيع "بسلاسة" جعل IEnumerable انها تتصرف مثل Lisp "القوائم" (التي تستخدمها السيارات/مجلس الإنماء والإعمار تعداد بشكل متكرر).أيفقط تنفيذ "(cdr بعض IEnumerable)" سيكون يرثى له غير فعالة.

يمكن لأي شخص أن يقترح واقعية ومفيدة ، مثال IEnumerable الكائن الذي لن تكون قادرة على تقديم "استنساخ()" الأسلوب ؟ هو أنه سوف يكون هناك مشكلة مع "العائد" بناء?

يمكن لأي شخص أن يقترح حلا?

هل كانت مفيدة؟

المحلول

المنطق هو لا يرحم! 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;
    }
}

علما بأن ToEnumerable طريقة يمنحك سهولة الاستخدام في المعيار C# الطريقة.

للإجابة على سؤالك:

أي شخص يمكن أن تشير إلى واقعية ، مفيدة, مثال IEnumerable الكائن الذي لن تكون قادرة على تقديم "استنساخ()" الأسلوب ؟ هو أنه سوف يكون هناك مشكلة مع "العائد" بناء?

وهو IEnumerable يمكن أن تذهب في أي مكان في العالم عن البيانات.هنا مثال أن يقرأ خطوط من وحدة التحكم:

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

هناك نوعان من المشاكل مع هذا:أولا ، Clone وظيفة قد لا تكون واضحة بشكل خاص في كتابة (، Reset لن يكون لها معنى).ثانيا: التسلسل اللانهائي - الذي هو المسموح به تماما.تسلسل كسالى.

مثال آخر:

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

لكل من هذه الأمثلة ، "الحل البديل" لقد قبلت لن تستخدم كثيرا ، لأنه فقط العادم المتاحة الذاكرة أو أغلق إلى الأبد.ولكن هذه هي صالحة تماما أمثلة من متواليات.

فهم C# و# متواليات ، تحتاج إلى إلقاء نظرة على القوائم في هاسكل, لا قوائم في المخطط.

في حال كنت تعتقد أن اللانهائي الاشياء هو ذر الرماد في العيون ، ماذا عن قراءة بايت من مأخذ:

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

إذا كان هناك بعض عدد وحدات البايت المرسلة إلى أسفل المقبس, هذا لن يكون تسلسل لانهائي.و بعد كتابة استنساخ لذلك سيكون من الصعب جدا.كيف مترجم توليد IEnumerable تنفيذ للقيام بذلك تلقائيا ؟

في أقرب وقت كما كان هناك استنساخ إنشاء كلتا الحالتين سوف تضطر الآن إلى العمل من منطقة عازلة مع نظام مشترك.هذا ممكن ولكن في واقع الامر انه ليس في حاجة - هذه ليست الطريقة هذه الأنواع من تسلسل مصممة ليتم استخدامها.عاملهم بحتة "وظيفيا" ، مثل القيم, تطبيق الفلاتر عليها بشكل متكرر بدلا من "حتما" تذكر مكان في التسلسل.انها قليلا أكثر نظافة من مستوى منخفض car/cdr التلاعب.

سؤال آخر:

أتساءل ما هو أدنى مستوى "البدائية(ق)" أنا أحتاج مثل هذا أي شيء قد ترغب في القيام به مع IEnumerable في مخطط مترجم يمكن تنفيذها في مخطط بدلا من ذلك من builtin.

الجواب القصير أعتقد أن ننظر في Abelson و سوسمان و على وجه الخصوص الجزء تيارات. IEnumerable هو تيار غير قائمة.وهي تصف كيف كنت بحاجة إلى إصدارات خاصة من الخريطة, فلتر, تتراكم, الخ.للعمل معهم.أنها أيضا الحصول على فكرة توحيد القوائم و الجداول في القسم 4.2.

نصائح أخرى

كحل مؤقت ، يمكن بسهولة جعل امتداد طريقة IEnumerator التي لم الاستنساخ.فقط إنشاء قائمة من العداد ، واستخدام عناصر كأعضاء.

كنت تفقد قدرات يتدفقون من العداد ، على الرغم - بما أنك جديدة "استنساخ" من شأنه أن يسبب الأولى العداد إلى تقييم كامل.

إذا كنت يمكن أن اسمحوا الأصلي العداد الذهاب ، أي.لا تستخدم أي أكثر من ذلك ، يمكنك تنفيذ "استنساخ" وظيفة أن يأخذ الأصلي العداد ، ويستخدم كمصدر للحصول على واحد أو أكثر من التعداد.

وبعبارة أخرى ، هل يمكن بناء شيء مثل هذا:

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

هذه يمكن داخليا حصة الأصلي العداد ، قائمة مرتبطة ، لتتبع المذكورة القيم.

هذا سيسمح:

  • لانهائية متواليات ، ما دام كل التعداد التقدم إلى الأمام (القائمة المرتبطة سيتم كتابة مثل هذا مرة واحدة كل التعداد مرت من نقطة محددة ، يمكن أن تكون تلك GC رائد)
  • كسول تعداد أول اثنين من التعداد التي تحتاج القيمة التي لم يتم استردادها من الأصلي العداد بعد ذلك أن الحصول عليها وتخزينها في قائمة مرتبطة الغلة قبل ذلك

المشكلة هنا هو بالطبع أنه لا يزال بحاجة إلى الكثير من الذاكرة إذا كان أحد العدادين التحرك الآن قبل الآخر.

هنا هو رمز المصدر.إذا كنت تستخدم التخريب ، يمكنك تحميل Visual Studio 2008 حل الملف مع مكتبة فئة مع رمز أدناه ، وكذلك وحدة منفصلة اختبار porject.

مستودع: 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.

هذا قد يساعد.فإنه يحتاج بعض التعليمات البرمجية استدعاء Dispose() على IEnumerator:

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

الغرض من "clonable" التعداد هو أساسا أن تكون قادرة على إنقاذ التكرار موقف يكون قادرا على العودة إليها في وقت لاحق.فإن هذا يعنى أن يتحرك الحاوية يجب أن توفر المزيد من واجهة غنية من مجرد IEnumerable.هو في الواقع شيء بين IEnumerable و IList.العمل مع IList يمكنك فقط استخدام عدد صحيح حسب مؤشر العداد ، أو إنشاء بسيطة ثابتة التفاف الطبقة ، وعقد إشارة إلى قائمة والموقف الحالي.

إذا حاوية الخاص بك لا يدعم الوصول العشوائي و يمكن إلا أن يتحرك إلى الأمام (مثل واحد اتجاهي قائمة مرتبطة) ، فإنه يجب على الأقل توفير القدرة على الحصول على العنصر التالي ، وبعد إشارة إلى السابق أو إلى بعض "التكرار الدولة" التي يمكن أن تعقد في التكرار.لذا واجهة يمكن أن تبدو مثل هذا:

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

نلاحظ أن التكرار هو غير قابل للتغيير, ، لا يمكن أن يكون "إلى الأمام", إلا أننا يمكن أن نسأل iterable الحاوية على مكرر الإشارة إلى الموضع التالي.الفائدة من ذلك هو أنه يمكنك تخزين التكرار في أي مكان طالما أنت بحاجة ، على سبيل المثال لديك كومة من التكرار والعودة إلى سبق أنقذ الموقف عندما كنت في حاجة.يمكنك حفظ الوظيفة الحالية لاستخدامها في وقت لاحق عن طريق تعيين إلى متغير, تماما كما كنت تفعل مع فهرس عدد صحيح.

على 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 الحالات تتكرر في كل enumerable و العداد الطبقات.هل سيكون إجبار العداد للحفاظ على دولة إضافية تتجاوز ما هو مطلوب.

على سبيل المثال, تخيل العداد على قائمة مرتبطة.الأساسية تنفيذ IEnumerable, تلك الفئة فقط بحاجة إلى أن نضع إشارة إلى العقدة الحالية.ولكن لدعم استنساخ الخاص بك, سوف تحتاج أيضا إلى الحفاظ على إشارة إلى رئيس قائمة شيء خلاف ذلك ليس له استخدام*.لماذا إضافيا من الدولة إلى العداد ، عندما يمكنك الذهاب إلى المصدر (IEnumerable) والحصول على آخر العداد?

و لماذا ضعف عدد مسارات التعليمات البرمجية أنت بحاجة إلى الاختبار ؟ في كل مرة كنت جعل طريقة جديدة لتصنيع كائن ، إضافة التعقيد.

* سوف تحتاج أيضا المؤشر الرئيسي إذا كنت تنفيذ إعادة تعيين ، ولكن وفقا مستندات, إعادة تعيين فقط هناك COM إمكانية التشغيل المتداخل و أنت حر في رمي NotSupportedException.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top