Какова цель/преимущество использовать итераторы возврата урожая в C#?

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

function MyObject(arg1, arg2, ...) {
  //constructor code using constructor arguments...
  //create/access public variables as 
  // this.var1 = foo;

  //private variables

  var v1;
  var v2;

  //private functions
  function privateOne() {
  }

  function privateTwon() {
  }

  //public functions

  MyObject.prototype.publicOne = function () {
  };

  MyObject.prototype.publicTwo = function () {
  };
}
76
задан CoderDennis 6 July 2009 в 18:11
поделиться

9 ответов

But what if you were building a collection yourself?

In general, iterators can be used to lazily generate a sequence of objects. For example Enumerable.Range method does not have any kind of collection internally. It just generates the next number on demand. There are many uses to this lazy sequence generation using a state machine. Most of them are covered under functional programming concepts.

In my opinion, if you are looking at iterators just as a way to enumerate through a collection (it's just one of the simplest use cases), you're going the wrong way. As I said, iterators are means for returning sequences. The sequence might even be infinite. There would be no way to return a list with infinite length and use the first 100 items. It has to be lazy sometimes. Returning a collection is considerably different from returning a collection generator (which is what an iterator is). It's comparing apples to oranges.

Hypothetical example:

static IEnumerable<int> GetPrimeNumbers() {
   for (int num = 2; ; ++num) 
       if (IsPrime(num))
           yield return num;
}

static void Main() { 
   foreach (var i in GetPrimeNumbers()) 
       if (i < 10000)
           Console.WriteLine(i);
       else
           break;
}

This example prints prime numbers less than 10000. You can easily change it to print numbers less than a million without touching the prime number generation algorithm at all. In this example, you can't return a list of all prime numbers because the sequence is infinite and the consumer doesn't even know how many items it wants from the start.

116
ответ дан 24 November 2019 в 11:12
поделиться

The fine answers here suggest that a benefit of yield return is that you don't need to create a list; Lists can be expensive. (Also, after a while, you'll find them bulky and inelegant.)

But what if you don't have a List?

yield return allows you to traverse data structures (not necessarily Lists) in a number of ways. For example, if your object is a Tree, you can traverse the nodes in pre- or post- order without creating other lists or changing the underlying data structure.

public IEnumerable<T> InOrder()
{
    foreach (T k in kids)
        foreach (T n in k.InOrder())
            yield return n;
    yield return (T) this;
}

public IEnumerable<T> PreOrder()
{
    yield return (T) this;
    foreach (T k in kids)
        foreach (T n in k.PreOrder())
            yield return n;
}
24
ответ дан 24 November 2019 в 11:12
поделиться

Вот мой предыдущий принятый ответ на тот же вопрос:

Значение ключевого слова Yield добавлено?

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

Теперь я могу упростить эту задачу для себя как автора парсера, применив подход SAX, в котором у меня есть интерфейс обратного вызова, который я уведомляю всякий раз, когда нахожу следующий фрагмент шаблона. Итак, в случае SAX каждый раз, когда я нахожу начало элемента, я вызываю метод beginElement и так далее.

Но это создает проблемы для моих пользователей. Они должны реализовать интерфейс обработчика, и поэтому они должны написать класс конечного автомата, который отвечает на методы обратного вызова. Это сложно сделать правильно, поэтому проще всего использовать стандартную реализацию, которая строит дерево DOM, и тогда они будут иметь возможность ходить по дереву. Но затем вся структура буферизуется в памяти - не очень хорошо.

Но как насчет того, чтобы вместо этого я написал свой синтаксический анализатор как метод итератора?

IEnumerable<LanguageElement> Parse(Stream stream)
{
    // imperative code that pulls from the stream and occasionally 
    // does things like:

    yield return new BeginStatement("if");

    // and so on...
}

Это будет не сложнее, чем подход с интерфейсом обратного вызова - просто выведите return объект, производный от моего базового класса LanguageElement , вместо вызова метода обратного вызова.

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

2
ответ дан 24 November 2019 в 11:12
поделиться

Lazy Evaluation/Deferred Execution

The "yield return" iterator blocks won't execute any of the code until you actually call for that specific result. This means they can also be chained together efficiently. Pop quiz: how many times will the following code iterate over the file?

var query = File.ReadLines(@"C:\MyFile.txt")
                            .Where(l => l.Contains("search text") )
                            .Select(l => int.Parse(l.SubString(5,8))
                            .Where(i => i > 10 );

int sum=0;
foreach (int value in query) 
{
    sum += value;
}

The answer is exactly one, and that not until way down in the foreach loop. Even though I have three separate linq operator functions, we still only loop through the contents of the file one time.

This has benefits other than performance. For example, I can write a fair simple and generic method to read and pre-filter a log file once, and use that same method in several different places, where each use adds on different filters. Thus, I maintain good performance while also efficiently re-using code.

Infinite Lists

See my answer to this question for a good example:
C# fibonacci function returning errors

Basically, I implement the fibonacci sequence using an iterator block that will never stop (at least, not before reaching MaxInt), and then use that implementation in a safe way.

Improved Semantics and separation of concerns

Again using the file example from above, we can now easily separate the code that reads the file from the code that filters out un-needed lines from the code that actually parses the results. That first one, especially, is very re-usable.

This is one of those things that's much harder to explain with prose than it is to just who with a simple visual1:

Imperative vs Functional Separation of Concerns

If you can't see the image, it shows two versions of the same code, with background highlights for different concerns. The linq code has all of the colors nicely grouped, while the traditional imperative code has the colors intermingled. The author argues (and I concur) that this result is typical of using linq vs using imperative code... that linq does a better job organizing your code to have a better flow between sections.


1 I believe this to be the original source: https://twitter.com/mariofusco/status/571999216039542784. Also note that this code is Java, but the C# would be similar.

16
ответ дан 24 November 2019 в 11:12
поделиться

If the entire list is gigantic, it might eat a lot of memory just to sit around, whereas with the yield you only play with what you need, when you need it, regardless of how many items there are.

2
ответ дан 24 November 2019 в 11:12
поделиться

Take a look at this discussion on Eric White's blog (excellent blog by the way) on lazy versus eager evaluation.

2
ответ дан 24 November 2019 в 11:12
поделиться

В игрушечных / демонстрационных сценариях нет большой разницы. Но бывают ситуации, когда получение итераторов полезно - иногда весь список недоступен (например, потоки) или список требует больших вычислительных ресурсов и вряд ли понадобится полностью.

9
ответ дан 24 November 2019 в 11:12
поделиться

Используя yield return , вы можете перебирать элементы без создания списка. Если вам не нужен список, но вы хотите перебрать некоторый набор элементов, проще написать

foreach (var foo in GetSomeFoos()) {
    operate on foo
}

, чем

foreach (var foo in AllFoos) {
    if (some case where we do want to operate on foo) {
        operate on foo
    } else if (another case) {
        operate on foo
    }
}

. Вы можете поместить всю логику для определения того, хотите ли вы работать с foo внутри ваш метод с использованием yield return и цикла foreach может быть намного более кратким.

2
ответ дан 24 November 2019 в 11:12
поделиться

Иногда последовательности, которые вам нужно вернуть, просто слишком большой, чтобы поместиться в памяти. Например, месяца 3 назад я участвовал в проекте по миграции данных между базами MS SLQ. Данные были экспортированы в формате XML. Возврат доходности оказался весьма полезным с XmlReader . Это значительно упростило программирование. Например, предположим, что файл содержит 1000 элементов Customer - если вы просто прочитаете этот файл в память, потребуется сохранить их все в памяти одновременно, даже если они обрабатываются последовательно. Итак, вы можете использовать итераторы для обхода коллекции один за другим. В этом случае вам придется потратить только память на один элемент.

Как оказалось, использование XmlReader для нашего проекта было единственным способом заставить приложение работать - оно работало долго, но, по крайней мере, не подвешивало всю систему и не поднимало OutOfMemoryException . Конечно, вы можете работать с XmlReader без итераторов yield. Но итераторы значительно облегчили мне жизнь (я бы не стал писать код для импорта так быстро и без проблем). Посмотрите эту страницу , чтобы увидеть, как итераторы yield используются для решения реальных задач (а не только научных с бесконечными последовательностями).

10
ответ дан 24 November 2019 в 11:12
поделиться
Другие вопросы по тегам:

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