Почему IEnumerator's не может быть клонирован?

Когда вы используете node.js, вы можете просто использовать пул соединений, нет очевидных недостатков в пуле, но вы устанавливаете гораздо меньше соединений (и меньше накладных расходов).

Кроме того, все сводится к тому, сколько запросов на самом деле выполняется и сколько времени выполнения им требуется, вот некоторые из них:

Скажем, все 50k пользователей одновременно подключаются к сети и каждый пользователь совершает 1 взаимодействие в 5 с, и для его выполнения требуется 10 мс времени DB, а затем требуется 50k * 10 мс / 5 с = 100 одновременных соединений.

Конечно, вам нужно учитывать некоторые действительные числа, и отношение чтения к записи имеет большое значение (чтение может потенциально выполняться параллельно), и кэш может очень помочь, если чтение составляет большое количество запросов. [114 ]

В общем, вам вряд ли понадобится много, если все, что вы делаете, это просто выбираете и вставляете для каждого взаимодействия.

PS Этот пост дал интересное число: с пулом соединений, 5k connection = 100k одновременно работающих пользователей. Конечно, он / она не упомянул свой тип нагрузки, но вы можете видеть, что (число одновременных потоков / потоков соединений) может достигать десятков и сотен, когда имеется пул соединений.

9
задан Paul Hollingsworth 30 April 2009 в 17:44
поделиться

7 ответов

Логика неумолима! IEnumerable не поддерживает клон , и вам нужен клон , поэтому вы не должны использовать 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 #.

Чтобы ответить на ваш вопрос:

Может кто-нибудь предложить реалистичный , полезный пример IEnumerable объект, который не сможет обеспечить эффективный метод "Clone ()"? Это было бы проблемой с конструкция «yield»?

IEnumerable может отправлять свои данные в любую точку мира. Вот пример, который читает строки из консоли:

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

Есть две проблемы с этим: во-первых, функция Clone не была бы особенно простой для записи (и Сброс был бы бессмысленным ). Во-вторых, последовательность бесконечна - что вполне допустимо. Последовательности ленивы.

Другой пример:

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

Если в сокет отправлено некоторое количество байтов, это не будет бесконечной последовательностью. И все же писать клон для этого было бы очень сложно. Как компилятор сгенерирует реализацию IEnumerable, чтобы сделать это автоматически?

Как только был создан Clone, оба экземпляра теперь должны были работать из буферной системы, которую они совместно использовали. Это возможно, но на практике это не нужно - это не то, как эти типы последовательностей предназначены для использования. Вы рассматриваете их чисто «функционально», как значения, рекурсивно применяя к ним фильтры, а не «обязательно» запоминая расположение в последовательности. Это немного чище, чем низкоуровневая автомобильная / cdr манипуляция.

Следующий вопрос:

Интересно, "примитив (ы)" мне нужно, чтобы все, что я мог бы сделать с IEnumerable в моем Scheme интерпретаторе может быть реализовано в схеме, а чем в качестве встроенного.

Я думаю, что короткий ответ будет выглядеть в Абельсон и Суссман и, в частности, часть о потоках . IEnumerable - это поток, а не список. И они описывают, как вам нужны специальные версии карты, фильтра, накопления и т. Д. Для работы с ними. Они также попадают в идею объединения списков и потоков в разделе 4.2.

23
ответ дан 4 December 2019 в 07:35
поделиться

Это может помочь. Требуется некоторый код для вызова 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();
}
0
ответ дан 4 December 2019 в 07:35
поделиться

Почему бы не использовать этот метод расширения:

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

Это в основном создаст и вернет новый перечислитель без полной оценки оригинала.

Редактировать: Да, я неправильно прочитал. Павел прав, это будет работать только с IEnumerable.

0
ответ дан 4 December 2019 в 07:35
поделиться

В качестве обходного пути вы можете легко создать метод расширения для IEnumerator, который бы выполнял клонирование. Просто создайте список из перечислителя и используйте элементы в качестве членов.

Однако вы потеряете возможности потокового перечисления - поскольку вы новый «клон», первый перечислитель полностью выполнит оценку.

4
ответ дан 4 December 2019 в 07:35
поделиться

Уже есть способ создать новый перечислитель - так же, как вы создали первый: IEnumerable.GetEnumerator. Я не уверен, зачем вам нужен другой механизм, чтобы сделать то же самое.

И в духе принципа СУХОЙ мне любопытно, почему вы захотите взять на себя ответственность за создание нового IEnumerator экземпляры, которые будут дублироваться как в ваших перечислимых, так и в ваших классах перечислителей. Вы бы заставили перечислитель поддерживать дополнительное состояние сверх того, что требуется.

Например, представьте перечислитель для связанного списка. Для базовой реализации IEnumerable этому классу нужно будет только сохранить ссылку на текущий узел. Но для поддержки вашего клона ему также необходимо сохранить ссылку на заголовок списка - что-то, что в противном случае бесполезно для *. Зачем вам добавлять это дополнительное состояние в перечислитель, когда вы можете просто перейти к источнику (IEnumerable) и получить другой перечислитель?

И зачем вам удваивать количество путей кода, которые необходимо протестировать? Каждый раз, когда вы создаете новый способ изготовления объекта, вы добавляете сложность.

* Вам также понадобится указатель головы, если вы реализовали Reset, но в соответствии с документами , Reset доступен только для взаимодействия COM, и вы можете создать исключение NotSupportedException.

-2
ответ дан 4 December 2019 в 07:35
поделиться

Если вы можете отпустить исходный счетчик, т.е. больше не использовать его, вы можете реализовать функцию "клонирования", которая берет исходный перечислитель и использует его в качестве источника для одного или нескольких перечислителей.

Другими словами, вы можете создать что-то вроде этого:

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

Эти могли бы совместно использовать исходный счетчик и связанный список для отслеживания перечисленных значений.

Это позволило бы:

  • Бесконечные последовательности, пока оба счетчика продвигаются вперед (связанный список будет записан так, что как только оба перечислителя прошли определенную точку, они могут быть объединены в сборку мусора)
  • Ленивое перечисление, первое из двух перечислителей, которым требуется значение, которое еще не было получено из исходного перечислителя, он получит его и сохранит в связанном списке, прежде чем выдать его

. Проблема, конечно, в том, что ему все равно потребуется много памяти, если один из счетчиков продвинется далеко впереди другого.

Вот исходный код. Если вы используете 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();
    }
}
3
ответ дан 4 December 2019 в 07:35
поделиться

Это использует отражение для создания нового экземпляра, а затем устанавливает значения на новом экземпляре. Я также нашел эту главу из C# in Depth очень полезной. Детали реализации блока Iterator: автоматически генерируемые машины состояний

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, сохраняя копию состояния итерации?

1
ответ дан 4 December 2019 в 07:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: