Yield: что, где и зачем (2024)

Сообщество .Net разработчиков замерло в ожидании выхода C# 7.0 и новых фич которые он принесет. Каждая версия языка которому уже в следующем году исполнится 15 лет принесла с собой что-то новое и полезное. И хотя каждая фича достойна отдельного упоминания, сегодня я хочу поговорить о ключевом слове yield. Я заметил, что начинающие разрабочики (и не только) избегают его использования. В этой статье я постараюсь донести преимущества и недостатки, а также выделить случаи, когда применение yield целесообразно.


yield создает итератор и позволяет нам не писать отдельный класс когда мы реализуем IEnumerable. C# содержит два выражения использующих yield: yield return <expression> и yield break. yield может применяться в методах, операторах и свойствах. Я буду говорить о методах, так как yield работает везде одинаково.


Применяя yield return мы декларируем, что данный метод возвращает последовательность IEnumerable, элементами которой являются результаты выражений каждого из yield return. Причем с возвращением значения, yield return передает управление вызывающей стороне и продолжает исполнение метода после запроса следующего элемента. Значения переменных внутри метода с yield сохраняются между запросами. yield break в свою очередь играет роль хорошо известного break используемого внутри циклов. Пример ниже вернет последовательность чисел от 0 до 10:


GetNumbers

private static IEnumerable<int> GetNumbers() { var number = 0; while (true) { if (number > 10) yield break; yield return number++; }}

Важно упомянуть, что у применения yield есть несколько ограничений, о которых нужно знать. Вызов Reset у итератора бросает NotSupportedException. Мы не можем использовать его в анонимных методах и методах содержащих unsafe код. Так же, yield return не может располагаться в блоке try-catch, хотя ничто не мешает разместить его в секции try блока try-finally. yield break может располагаться в секции try как try-catch так и try-finally. Причины таких ограничений я приводить не буду, так как они детально изложены Эриком Липертом здесь и здесь.


Давайте посомтрим во что превращается yield после компиляции. Каждый метод с yield return представляет собой машину состояний, которая переходит из одного состояния в другое в процессе работы итератора. Ниже приведено простое приложение, которое выводит в консоль бесконечную последовательность нечетных чисел:


Пример программы

internal class Program{ private static void Main() { foreach (var number in GetOddNumbers()) Console.WriteLine(number); } private static IEnumerable<int> GetOddNumbers() { var previous = 0; while (true) if (++previous%2 != 0) yield return previous; }}

Компилятор сгенерирует следующий код:


Сгенерированный код

internal class Program{ private static void Main() { IEnumerator<int> enumerator = null; try { enumerator = GetOddNumbers().GetEnumerator(); while (enumerator.MoveNext()) Console.WriteLine(enumerator.Current); } finally { if (enumerator != null) enumerator.Dispose(); } } [IteratorStateMachine(typeof(CompilerGeneratedYield))] private static IEnumerable<int> GetOddNumbers() { return new CompilerGeneratedYield(-2); } [CompilerGenerated] private sealed class CompilerGeneratedYield : IEnumerable<int>, IEnumerable, IEnumerator<int>, IDisposable, IEnumerator { private readonly int _initialThreadId; private int _current; private int _previous; private int _state; [DebuggerHidden] public CompilerGeneratedYield(int state) { _state = state; _initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] IEnumerator<int> IEnumerable<int>.GetEnumerator() { CompilerGeneratedYield getOddNumbers; if ((_state == -2) && (_initialThreadId == Environment.CurrentManagedThreadId)) { _state = 0; getOddNumbers = this; } else { getOddNumbers = new CompilerGeneratedYield(0); } return getOddNumbers; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<int>)this).GetEnumerator(); } int IEnumerator<int>.Current { [DebuggerHidden] get { return _current; } } object IEnumerator.Current { [DebuggerHidden] get { return _current; } } [DebuggerHidden] void IDisposable.Dispose() { } bool IEnumerator.MoveNext() { switch (_state) { case 0: _state = -1; _previous = 0; break; case 1: _state = -1; break; default: return false; } int num; do { num = _previous + 1; _previous = num; } while (num%2 == 0); _current = _previous; _state = 1; return true; } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } }}

Из примера видно, что тело метода с yield было заменено сгенерированным классом. Локальные переменные метода превратились в поля класса. Сам класс реализует как IEnumerable так и IEnumerator. Метод MoveNext содержит логику замененного метода с тем лишь отличием, что она представлена в виде машины состояний. В зависимости от реализации изначального метода, сгенерированный класс может дополнительно содержать реализацию метода Dispose.


Проведем два теста и замерим производительность и потребление памяти. Сразу отмечу — эти тесты синтетические и приводятся только чтоб продемонстрировать работу yield в сравнении с реализацией "в лоб". Замеры будем делать с помощью BenchmarkDotNet с включеным модулем диагностики BenchmarkDotNet.Diagnostics.Windows. Первым сравним скорость работы метода получения последовательности чисел (аналог Enumerable.Range(start, count)). В первом случае будет реализация без итератора, во втором с:


Тест 1

public int[] Array(int start, int count) { var numbers = new int[count]; for (var i = 0; i < count; ++i) numbers[i] = start + i; return numbers;}public int[] Iterator(int start, int count) { return IteratorInternal(start, count).ToArray();}private IEnumerable<int> IteratorInternal(int start, int count) { for (var i = 0; i < count; ++i) yield return start + i;}

MethodCountStartMedianStdDevGen 0Gen 1Gen 2Bytes Allocated/Op
Array1001091.19 ns1.25 ns385.01--169.18
Iterator100101,173.26 ns10.94 ns1,593.00--700.37

Как видно из результатов, реализация Array на порядок быстрее и потребляет в 4 раза меньше памяти. Итератор и отдельный вызов ToArray сделали свое дело.


Второй тест будет более сложным. Мы сэмулируем работу с потоком данных. Мы будем сначала выбирать записи с нечетным ключем, а затем с ключем кратным 3-м. Как и в предыдущем тесте, первая реализация будет без итератора, вторая с:


Тест 2

public List<Tuple<int, string>> List(int start, int count) { var odds = new List<Tuple<int, string>>(); foreach (var record in OddsArray(ReadFromDb(start, count))) if (record.Item1%3 == 0) odds.Add(record); return odds;}public List<Tuple<int, string>> Iterator(int start, int count) { return IteratorInternal(start, count).ToList();}private IEnumerable<Tuple<int, string>> IteratorInternal(int start, int count) { foreach (var record in OddsIterator(ReadFromDb(start, count))) if (record.Item1%3 == 0) yield return record;}private IEnumerable<Tuple<int, string>> OddsIterator(IEnumerable<Tuple<int, string>> records) { foreach (var record in records) if (record.Item1%2 != 0) yield return record;}private List<Tuple<int, string>> OddsArray(IEnumerable<Tuple<int, string>> records) { var odds = new List<Tuple<int, string>>(); foreach (var record in records) if (record.Item1%2 != 0) odds.Add(record); return odds;}private IEnumerable<Tuple<int, string>> ReadFromDb(int start, int count) { for (var i = start; i < count; ++i) yield return new KeyValuePair<int, string>(start + i, RandomString());}private static string RandomString() { return Guid.NewGuid().ToString("n");}

MethodCountStartMedianStdDevGen 0Gen 1Gen 2Bytes Allocated/Op
List1001043.14 us0.14 us279.04--4,444.14
Iterator1001043.22 us0.76 us231.00--3,760.96

В данном случае, скорость выполнения оказалась одинаковой, а потребление памяти yield оказалось даже ниже. Это связано с тем, что в реализации с итератором коллекция вычислилась только единожды и мы сэкономили память на аллокации одного List<Tuple<int, string>>.


Беря во внимание все сказанное ранее и приведенные тесты, можно сделать краткий вывод: основной недостаток yield — это дополнительный класс итератор. Если последовательность конечная, а вызывающая сторона не выполняет сложных манипуляций с элементами, итератор будет медленнее и создаст нежелательную нагрузку на GC. Применять же yield целесобразно в случаях обработки длинных последовательностей, когда каждое вычисление коллекции приводит к аллокации больших массивов памяти. Ленивая природа yield позволяет избежать вычисления элементов последовательности, которые могут быть отфильтрованы. Это может радикально сократить потребление памяти и уменьшить нагрузку на процессор.

Yield: что, где и зачем (2024)
Top Articles
Søren Ryges kringle (opskrift fra Anne Margrethe i Hirtshals) - Madens Verden
Gebetszeit: Wie beten die Katholiken?
Www.paystubportal.com/7-11 Login
123 Movies Black Adam
Www.craigslist Virginia
Katmoie
Body Rubs Austin Texas
Rabbits Foot Osrs
Noaa Swell Forecast
GAY (and stinky) DOGS [scat] by Entomb
Bustle Daily Horoscope
Southland Goldendoodles
Think Up Elar Level 5 Answer Key Pdf
Wilmot Science Training Program for Deaf High School Students Expands Across the U.S.
My.tcctrack
Star Wars: Héros de la Galaxie - le guide des meilleurs personnages en 2024 - Le Blog Allo Paradise
24 Hour Drive Thru Car Wash Near Me
Ibukunore
V-Pay: Sicherheit, Kosten und Alternativen - BankingGeek
Loft Stores Near Me
Babbychula
11 Ways to Sell a Car on Craigslist - wikiHow
Best Sports Bars In Schaumburg Il
Ou Football Brainiacs
Marilyn Seipt Obituary
Publix Near 12401 International Drive
Effingham Daily News Police Report
Superhot Free Online Game Unblocked
Kempsville Recreation Center Pool Schedule
Fairwinds Shred Fest 2023
Kaiser Infozone
L'alternativa - co*cktail Bar On The Pier
Eaccess Kankakee
Cars And Trucks Facebook
Edward Walk In Clinic Plainfield Il
Peter Vigilante Biography, Net Worth, Age, Height, Family, Girlfriend
Goodwill Thrift Store & Donation Center Marietta Photos
Black Adam Showtimes Near Amc Deptford 8
Linda Sublette Actress
Frommer's Philadelphia &amp; the Amish Country (2007) (Frommer's Complete) - PDF Free Download
Tyler Perry Marriage Counselor Play 123Movies
Danielle Ranslow Obituary
Bob And Jeff's Monticello Fl
Amc.santa Anita
Dontrell Nelson - 2016 - Football - University of Memphis Athletics
Unit 11 Homework 3 Area Of Composite Figures
Server Jobs Near
Otter Bustr
Dcuo Wiki
Craigslist.raleigh
Invitation Quinceanera Espanol
Booked On The Bayou Houma 2023
Latest Posts
Article information

Author: Duncan Muller

Last Updated:

Views: 6404

Rating: 4.9 / 5 (59 voted)

Reviews: 82% of readers found this page helpful

Author information

Name: Duncan Muller

Birthday: 1997-01-13

Address: Apt. 505 914 Phillip Crossroad, O'Konborough, NV 62411

Phone: +8555305800947

Job: Construction Agent

Hobby: Shopping, Table tennis, Snowboarding, Rafting, Motor sports, Homebrewing, Taxidermy

Introduction: My name is Duncan Muller, I am a enchanting, good, gentle, modern, tasty, nice, elegant person who loves writing and wants to share my knowledge and understanding with you.