Класс C # является IEnumerable И IEnumerator одновременно. Какие проблемы с этим? - PullRequest
7 голосов
/ 09 ноября 2011

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

Например, целочисленная реализация этого класса может проходить через следующее:

GenericPermutations<int> p = new GenericPermutations<int>({ 1, 2, 3 });
p.nextPermutation(); // 123
p.nextPermutation(); // 132
p.nextPermutation(); // 213
// etc.

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

ПРОБЛЕМА: В настоящее время я пытаюсь интегрировать IEnumerator и IEnumerable с этим классом, и мне кажется, что это должно быть так (вместо использования подкласса в качестве IEnumerable). До сих пор я избегал проблемы, пытаясь получить от него два перечислителя, передавая новый объект GenericPermutation в методе GetEnumerator.

Это плохая идея? Что-нибудь еще, что я должен рассмотреть?

Ответы [ 2 ]

9 голосов
/ 09 ноября 2011

Уменьшите вашу путаницу (?), Используя общие версии IEnumerable и IEnumerator.

Перечислимая перестановка равна IEnumerable<IEnumerable<T>>.Таким образом, у вас может быть что-то вроде

IEnumerable<IEnumerable<T>> GetPermutations(IEnumerable<T> sequence)
{
    return new Permuter<T>(sequence);
}

и

public class Permuter<T> : IEnumerable<IEnumerable<T>> { ... }

Более того, я видел более одного случая, когда один тип реализовывал и IEnumerable<T>, и IEnumerator<T>;его метод GetEnumerator был просто return this;.

Я думаю, что такой тип должен был бы быть структурой, потому что, если бы это был класс, у вас возникли бы все виды проблем, если бы вы вызывали GetEnumerator () aво второй раз до завершения первого перечисления.

РЕДАКТИРОВАТЬ: использование перестановщика

var permuter = GetPermutations(sequence);
foreach (var permutation in permuter)
{
    foreach (var item in permutation)
        Console.Write(item + "; ");
    Console.WriteLine();
}

Если предположить, что входная последовательность равна {1, 2, 3}, выходной сигнал равен

1; 2; 3; 
1; 3; 2; 
2; 1; 3; 
2; 3; 1; 
3; 1; 2; 
3; 2; 1; 

РЕДАКТИРОВАТЬ:

Вот супер-неэффективная реализация, чтобы проиллюстрировать предложение:

public class Permuter<T> : IEnumerable<IEnumerable<T>>
{
    private readonly IEnumerable<T> _sequence;

    public Permuter(IEnumerable<T> sequence)
    {
        _sequence = sequence;
    }

    public IEnumerator<IEnumerable<T>> GetEnumerator()
    {
        foreach(var item in _sequence)
        {
            var remaining = _sequence.Except(Enumerable.Repeat(item, 1));
            foreach (var permutation in new Permuter<T>(remaining))
                yield return Enumerable.Repeat(item, 1).Concat(permutation);
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}
1 голос
/ 27 января 2013

Один объект может вести себя как IEnumerator<T> и IEnumerable<T>, но объекту обычно трудно сделать это таким образом, чтобы избежать странной семантики;если IEnumerator<T> не будет без сохранения состояния (например, пустой перечислитель, где MoveNext() всегда возвращает false, или перечислитель с бесконечным повторением, где MoveNext() ничего не делает, но всегда возвращает true, а Current всегда возвращает то же самоезначение), каждый вызов GetEnumerator() должен возвращать отдельный экземпляр объекта, и, вероятно, будет мало смысла в том, чтобы этот экземпляр реализовывал IEnumerable<T>.

Наличие типа значения, реализующего IEnumerable<T> и IEnumerator<T>и наличие метода GetEnumerator(), возвращающего this, будет удовлетворять требованию, чтобы каждый вызов GetEnumerator возвращал отдельный экземпляр объекта, но наличие типов значений, реализующих изменяемые интерфейсы, как правило, опасно.Если тип значения упакован в IEnuerator<T> и никогда не распаковывается, он будет вести себя как объект типа класса, но нет реальной причины, по которой он не должен был быть просто объектом типа класса.

Итераторыв C # реализованы как объекты класса, которые реализуют как IEnumerable<T>, так и IEnumerator<T>, но они включают в себя немало причудливой логики для обеспечения семантической корректности.Чистый эффект состоит в том, что реализация одного объекта обоими интерфейсами обеспечивает небольшое улучшение производительности в обмен на значительную сложность в сгенерированном коде и некоторую семантическую причуду в их поведении IDisposable.Я не рекомендовал бы этот подход в любом коде, который должен быть удобочитаемым;поскольку аспекты класса IEnumerator<T> и IEnumerable<T> в основном используют разные поля, а комбинированный класс должен иметь поле «идентификатор потока», которое не потребуется при использовании отдельных классов, можно добиться повышения производительностииспользование одного и того же объекта для реализации для обоих интерфейсов ограничено.Возможно, стоит сделать это, если добавление сложности компилятору обеспечит это небольшое улучшение производительности для миллионов подпрограмм итератора, но не стоит делать это для повышения производительности одной подпрограммы.

...