Полезен ли yield за пределами LINQ?

Когда я думаю, что могу использовать ключевое слово yield, я делаю шаг назад и смотрю, как это повлияет на мой проект. Я всегда заканчиваю тем, что возвращаю коллекцию вместо того, чтобы уступать, потому что чувствую, что накладные расходы на поддержание состояния метода получения мне не очень выгодны. Почти во всех случаях, когда я возвращаю коллекцию, я чувствую, что в 90% случаев вызывающий метод будет перебирать все элементы в коллекции или будет искать серию элементов по всей коллекции.

Я понимаю его полезность в linq, но мне кажется, что только команда linq пишет такие сложные запрашиваемые объекты, которые приносят пользу.

Кто-нибудь писал что-нибудь вроде linq или нет, где yield был полезен?


person Bob    schedule 25.11.2008    source источник
comment
Вы имели в виду за пределами Linq или IEnumerable? Я должен представить, что использование yield иначе, чем в счетчиках, будет довольно редким (и интересным). Джон Скит упоминает об одном в своей книге ...   -  person Benjol    schedule 04.06.2009
comment
очень интересное использование yield находится в библиотеке Power Threading Джеффри Рихтера.   -  person Yuriy Zanichkovskyy    schedule 27.01.2010


Ответы (14)


Недавно мне пришлось представить математические выражения в виде класса Expression. При оценке выражения я должен пройти по древовидной структуре с помощью древовидной прогулки после заказа. Для этого я реализовал IEnumerable ‹T› следующим образом:

public IEnumerator<Expression<T>> GetEnumerator()
{
    if (IsLeaf)
    {
        yield return this;
    }
    else
    {
        foreach (Expression<T> expr in LeftExpression)
        {
            yield return expr;
        }
        foreach (Expression<T> expr in RightExpression)
        {
            yield return expr;
        }
        yield return this;
    }
}

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

person Morten Christiansen    schedule 25.11.2008
comment
C # действительно нужно ключевое слово yieldcollection, чтобы абстрагироваться от циклов foreach (x в коллекции) {yield x}, которые в наши дни каждый пишет 100x в день :-( - person Orion Edwards; 03.12.2008
comment
если вы просто выполняете foreach (x в коллекции) {yield return x;} ... вы можете просто выполнить .Select (x = ›x). если вы хотите работать с набором элементов в коллекции, вы можете создать метод расширения .Foreach ‹T, TResult› (действие IEnumerable ‹T› col, Action ‹T, TResult› действие) - person Matthew Whited; 27.01.2010

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

Возьмем, например, итератор фильтра:

IEnumerator<T>  Filter(this IEnumerator<T> coll, Func<T, bool> func)
{
     foreach(T t in coll)
        if (func(t))  yield return t;
}

Теперь вы можете связать это:

 MyColl.Filter(x=> x.id > 100).Filter(x => x.val < 200).Filter (etc)

Ваш метод будет создавать (и бросать) три списка. Мой метод повторяется только один раз.

Кроме того, когда вы возвращаете коллекцию, вы навязываете пользователям определенную реализацию. Итератор более общий.

person James Curran    schedule 25.11.2008
comment
Но разве не было бы проще выполнить этот фильтр с помощью linq? - person Bob; 25.11.2008
comment
Этот фильтр в основном и есть метод расширения LINQ Where. - person Thedric Walker; 25.11.2008
comment
Это моя точка зрения, я думаю, было бы проще использовать linq, вы бы когда-нибудь писали этот код фильтрации вместо использования linq? Какие преимущества вы получите? - person Bob; 25.11.2008
comment
@Bob Linq - это запрос интеграции языка, то есть, в частности, ключевые слова from, where orderby и т. Д. Они заменяются компилятором на связанное выражение, подобное тому, которое было в ответе. Они эквивалентны. Метод Filter был включен в качестве примера. - person James Curran; 16.09.2015

Я понимаю его полезность в linq, но мне кажется, что только команда linq пишет такие сложные запрашиваемые объекты, которые приносят пользу.

Yield был полезен сразу после его реализации в .NET 2.0, что было задолго до того, как кто-либо подумал о LINQ.

Зачем мне писать эту функцию:

IList<string> LoadStuff() {
  var ret = new List<string>();
  foreach(var x in SomeExternalResource)
    ret.Add(x);
  return ret;
}

Когда я могу использовать yield и сэкономить усилия и сложность создания временного списка без уважительной причины:

IEnumerable<string> LoadStuff() {
  foreach(var x in SomeExternalResource)
    yield return x;
}

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

Я мог бы продолжать и продолжать ....

person Orion Edwards    schedule 02.12.2008
comment
Я действительно считаю, что Андерс Хейлсберг работал над Linq несколько лет назад. - person Tom Stickel; 18.11.2011

В предыдущей компании я писал такие циклы:

for (DateTime date = schedule.StartDate; date <= schedule.EndDate; 
     date = date.AddDays(1))

С помощью очень простого блока итератора я смог изменить это на:

foreach (DateTime date in schedule.DateRange)

Это сделало код намного проще для чтения, IMO.

person Jon Skeet    schedule 25.11.2008
comment
Вау - код Джона Скита, с которым я не согласен! = X Из первого примера очевидно, что вы повторяете несколько дней, но эта ясность отсутствует во втором. Я бы использовал что-то вроде schedule.DateRange.Days (), чтобы избежать двусмысленности. - person Erik Forbes; 25.11.2008
comment
Конечно, для этого потребуется нечто большее, чем просто реализация одного свойства. Я бы сказал, что очевидно, что DateRange - это диапазон дат, то есть дней, но это субъективная вещь. Это могло быть названо Dates, а не DateRange - не уверен. В любом случае, он менее пушистый, чем оригинал. - person Jon Skeet; 25.11.2008
comment
Да, это правда. пожимает плечами Я бы лично не был удовлетворен этим, но если это понятно автору и любым будущим сопровождающим, то это не имеет особого значения. - person Erik Forbes; 25.11.2008
comment
Кроме того, я просто кослюсь - ваш пример демонстрирует полезность блоков итератора, и это важно для этого вопроса. Извините за придирку. = X - person Erik Forbes; 25.11.2008
comment
Nitpicking - это хорошо, прясть волосы - это хорошо, комментарии и предложения по стилю кодирования всегда приветствуются :) - person Jon Skeet; 25.11.2008
comment
Я бы не согласился с тем, что цикл for на самом деле более ясно показывает, что это дни, чем цикл foreach. StartDate и EndDate по-прежнему относятся к значениям DateTime ... которые неявно подразумевают дни. У вас может быть несколько значений DateTime в один день для разных часов. Единственный истинный источник дня в версии цикла for - это поведение ВНУТРИ цикла ... а не сам цикл. Если бы тот же код использовался внутри цикла foreach, там была бы такая же ясность. - person jrista; 15.02.2013
comment
@jrista: Для меня того факта, что объекту присвоено имя DateRange, достаточно, чтобы сказать, что это даты. Конечно, в наши дни я бы очень хотел, чтобы это был IEnumerable<LocalDate>, использующий Noda Time :) - person Jon Skeet; 16.02.2013
comment
Да, перечисление LocalDate определенно устранит любую двусмысленность. :) Мне действительно интересно, как контекст влияет на восприятие в подобных случаях. Случай for имел более богатый контекст, чем случай foreach, с заметной разницей в воспринимаемой ясности и интерпретации (несмотря на отсутствие какой-либо реальной разницы между двумя циклами вообще). Я думаю, что контекст фрагмента кода часто игнорируется или слабо осознается концепцией, которая может быть весьма критичной для понимания этого кода широкой базой читателей. - person jrista; 16.02.2013
comment
Что, кстати, я не говорю, чтобы унизить ваш ответ. Я просто имею в виду в более широком контексте написания кода, который удобен в обслуживании и понятен. ;П - person jrista; 16.02.2013

yield был разработан для C # 2 (до Linq в C # 3).

Мы активно использовали его в крупном корпоративном веб-приложении на C # 2, когда имели дело с доступом к данным и часто повторяющимися вычислениями.

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

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

По сути, это то, что делает SqlDataReader - это настраиваемый перечислитель, работающий только в прямом направлении.

Что yield позволяет вам сделать, так это быстро и с минимальным количеством кода написать свои собственные счетчики.

Все, что делает yield, можно было бы сделать на C # 1 - для этого потребовалась масса кода.

Linq действительно максимизирует ценность поведения yield, но это, конечно, не единственное приложение.

person Keith    schedule 25.11.2008

Всякий раз, когда ваша функция возвращает IEnumerable, вы должны использовать «уступку». Не только в .Net> 3.0.

.Net 2.0 пример:

  public static class FuncUtils
  {
      public delegate T Func<T>();
      public delegate T Func<A0, T>(A0 arg0);
      public delegate T Func<A0, A1, T>(A0 arg0, A1 arg1);
      ... 

      public static IEnumerable<T> Filter<T>(IEnumerable<T> e, Func<T, bool> filterFunc)
      {
          foreach (T el in e)
              if (filterFunc(el)) 
                  yield return el;
      }


      public static IEnumerable<R> Map<T, R>(IEnumerable<T> e, Func<T, R> mapFunc)
      {
          foreach (T el in e) 
              yield return mapFunc(el);
      }
        ...
person macropas    schedule 25.11.2008

Я не уверен насчет реализации yield () в C #, но на динамических языках это намного эффективнее, чем создание всей коллекции. во многих случаях это упрощает работу с наборами данных, намного превышающими размер ОЗУ.

person Javier    schedule 25.11.2008

Я большой поклонник Yield в C #. Это особенно верно в больших доморощенных фреймворках, где часто методы или свойства возвращают List, который является подмножеством другого IEnumerable. Я вижу следующие преимущества:

  • возвращаемое значение метода, использующего yield, неизменяемо
  • вы выполняете итерацию по списку только один раз
  • это переменная позднего или ленивого выполнения, то есть код для возврата значений не выполняется до тех пор, пока не понадобится (хотя это может укусить вас, если вы не знаете, что делаете)
  • изменений исходного списка вам не нужно вызывать, чтобы получить другой IEnumerable, вы просто снова перебираете IEnumeable
  • многое другое

Еще одно ОГРОМНОЕ преимущество yield - это когда ваш метод потенциально вернет миллионы значений. Так много, что существует вероятность нехватки памяти при построении списка до того, как метод сможет его вернуть. С yield метод может просто создавать и возвращать миллионы значений, и до тех пор, пока вызывающий объект также не сохраняет каждое значение. Так что это хорошо для крупномасштабных операций обработки / агрегирования данных.

person Turbo    schedule 27.01.2010

Лично я не обнаружил, что использую yield в обычном повседневном программировании. Однако недавно я начал играть с примерами Robotics Studio и обнаружил, что yield там широко используется, поэтому я также вижу, что он используется вместе с CCR (Concurrency and Coordination Runtime), где у вас есть проблемы с асинхронностью и параллелизмом.

Во всяком случае, я все еще пытаюсь осмыслить это.

person Harrison    schedule 25.11.2008

Урожай полезен, потому что экономит ваше пространство. Большинство оптимизаций в программировании сводятся к компромиссу между пространством (диск, память, сеть) и обработкой. Yield как программная конструкция позволяет выполнять итерацию по коллекции много раз подряд без необходимости создавать отдельную копию коллекции для каждой итерации.

рассмотрим этот пример:

static IEnumerable<Person> GetAllPeople()
{
    return new List<Person>()
    {
        new Person() { Name = "George", Surname = "Bush", City = "Washington" },
        new Person() { Name = "Abraham", Surname = "Lincoln", City = "Washington" },
        new Person() { Name = "Joe", Surname = "Average", City = "New York" }
    };
}

static IEnumerable<Person> GetPeopleFrom(this IEnumerable<Person> people,  string where)
{
    foreach (var person in people)
    {
        if (person.City == where) yield return person;
    }
    yield break;
}

static IEnumerable<Person> GetPeopleWithInitial(this IEnumerable<Person> people, string initial)
{
    foreach (var person in people)
    {
        if (person.Name.StartsWith(initial)) yield return person;
    }
    yield break;
}

static void Main(string[] args)
{
    var people = GetAllPeople();
    foreach (var p in people.GetPeopleFrom("Washington"))
    {
        // do something with washingtonites
    }

    foreach (var p in people.GetPeopleWithInitial("G"))
    {
        // do something with people with initial G
    }

    foreach (var p in people.GetPeopleWithInitial("P").GetPeopleFrom("New York"))
    {
        // etc
    }
}

(Очевидно, что вам не обязательно использовать yield с методами расширения, это просто создает мощную парадигму для размышлений о данных.)

Как видите, если у вас много этих "фильтровальных" методов (но это может быть любой метод, который работает со списком людей), вы можете связать многие из них вместе, не требуя дополнительного места для хранения на каждом этапе. . Это один из способов поднять уровень языка программирования (C #), чтобы лучше выразить ваши решения.

Первый побочный эффект yield заключается в том, что выполнение логики фильтрации задерживается до тех пор, пока она вам не понадобится. Поэтому, если вы создаете переменную типа IEnumerable ‹> (с доходностью), но никогда не выполняете итерацию по ней, вы никогда не выполняете логику и не потребляете пространство, что является мощной и бесплатной оптимизацией.

Другой побочный эффект заключается в том, что yield работает с самым низким общим интерфейсом коллекции (IEnumerable ‹>), что позволяет создавать библиотечный код с широким применением.

person Pieter Breed    schedule 25.11.2008
comment
Но все это действительно просто LINQ. Если вы используете .NET 3.5, вы наверняка реализуете GetPeopleWithInitial, вернув people.Where (person = ›person.Name.StartsWith (initial)). - person Jon Skeet; 25.11.2008
comment
ну да и нет. То, что вы говорите, верно, но вам везде нужно будет person = ›person.Name.Startswith (). С помощью библиотечного метода вы получаете очевидные преимущества ... yield также входит в .NET 2, тогда как не у всех еще есть .NET 3.5 ... - person Pieter Breed; 25.11.2008
comment
Питер: Я не говорю, что вам следует удалить библиотечные методы, но обычно я бы реализовал их с помощью LINQ. И когда это так близко к LINQ, на самом деле это не похоже на ответ на вопрос, когда yield полезен вне LINQ - переопределение LINQ самостоятельно не считается, ИМО :) - person Jon Skeet; 25.11.2008
comment
вам не нужен разрыв доходности, так как это последняя строка метода - person Scott Cowan; 25.11.2008

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

person Logicalmind    schedule 25.11.2008

Я использовал yeild в коде, отличном от linq, вроде этого (при условии, что функции не находятся в одном классе):

public IEnumerable<string> GetData()
{
    foreach(String name in _someInternalDataCollection)
    {
        yield return name;
    }
}

...

public void DoSomething()
{
    foreach(String value in GetData())
    {
        //... Do something with value that doesn't modify _someInternalDataCollection
    }
}

Вы должны быть осторожны, чтобы случайно не изменить коллекцию, которую выполняет ваша функция GetData (), иначе она вызовет исключение.

person Anton    schedule 25.11.2008

Урожайность очень полезна в целом. Он находится на рубине среди других языков, поддерживающих программирование в функциональном стиле, так что это похоже на привязку к linq. Скорее наоборот, linq функциональна по стилю, поэтому использует yield.

У меня была проблема, когда моя программа использовала много процессора в некоторых фоновых задачах. Что я действительно хотел, так это иметь возможность писать функции, как обычные, чтобы я мог легко их читать (то есть весь аргумент, основанный на потоках и событиях). И все же иметь возможность разбивать функции, если они занимали слишком много процессора. Урожайность идеально подходит для этого. Я написал сообщение в блоге об этом и источник доступен для всех :)

person Anders Rune Jensen    schedule 02.12.2008

Расширения System.Linq IEnumerable - это здорово, но иногда вам нужно больше. Например, рассмотрим следующее расширение:

public static class CollectionSampling
{
    public static IEnumerable<T> Sample<T>(this IEnumerable<T> coll, int max)
    {
        var rand = new Random();
        using (var enumerator = coll.GetEnumerator());
        {
            while (enumerator.MoveNext())
            {
                yield return enumerator.Current; 
                int currentSample = rand.Next(max);
                for (int i = 1; i <= currentSample; i++)
                    enumerator.MoveNext();
            }
        }
    }    
}

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

person Ohad Schneider    schedule 28.09.2010