пятница, 17 мая 2013 г.

Yield return – великі можливості маленького оператора :)

В силу того що оператор не користується великою популярність мало хто знає про те як він працює за сценою і це в свою чергу не дає нам чіткого розуміння коли його можна використати і які переваги це нам дасть … Такий собі замкнутий круг.



Отож головне що треба про нього знати так це те що код типу:
   1: private static IEnumerable<string> GetString()

   2: {

   3:     yield return "X";

   4:     yield return "XX";

   5:     yield return "XXX";

   6: }


фактично перетвориться в обєкт який наслідує


   1: private sealed class <GetInt> d__0 : IEnumerable<string>, IEnumerable, IEnumerator<string>, IEnumerator, IDisposable

і має дуже хитру реалізацію методу MoveNext()

Фішка в тому що цей метод містить такий код:


   1: bool IEnumerator.MoveNext()

   2: {

   3:   switch (this.<>1__state)

   4:   {

   5:     case 0:

   6:       this.<>1__state = -1;

   7:       this.<>2__current = "X";

   8:       this.<>1__state = 1;

   9:       return true;

  10:     case 1:

  11:       this.<>1__state = -1;

  12:       this.<>2__current = "XX";

  13:       this.<>1__state = 2;

  14:       return true;

  15:     case 2:

  16:       this.<>1__state = -1;

  17:       this.<>2__current = "XXX";

  18:       this.<>1__state = 3;

  19:       return true;

  20:     case 3:

  21:       this.<>1__state = -1;

  22:       break;

  23:   }

  24:   return false;

  25: }

Як бачимо при кожному виклику методу ми в 2__current записуємо необхідні нам дані та змінну яка відповідає за стан змінюємо так щоб вона вказувала на наступний кейс в свічі. Така собі простенька стейт машина.

Вигода стає явною коли нам треба сформувати специфічну колекцію в якій елементи будуть залежати від певних обставин(пора року, тип поточно юзера…)

Наприклад:


   1: private static IEnumerable<string> GetString()

   2: {

   3:     yield return "Like";

   4:     if (role == "admin")

   5:     {

   6:         yield return "Remove";

   7:     }

   8:     else

   9:     {

  10:         yield return "Spam";

  11:     }

  12:     yield return "Ignore";

  13: }

В даному випадку якщо на основі колекції формується меню то для юзера буде доступно поскаржитись на спам а для адміна зразу видалити запис. Приклад хоч і на думаний але дає зрозуміти суть.

в результаті ми отримаємо:


   1: bool IEnumerator.MoveNext()

   2:  {

   3:    switch (this.<>1__state)

   4:    {

   5:      case 0:

   6:        this.<>1__state = -1;

   7:        this.<>2__current = "Like";

   8:        this.<>1__state = 1;

   9:        return true;

  10:      case 1:

  11:        this.<>1__state = -1;

  12:        if (Program.role == "admin")

  13:        {

  14:          this.<>2__current = "Remove";

  15:          this.<>1__state = 2;

  16:          return true;

  17:        }

  18:        else

  19:        {

  20:          this.<>2__current = "Spam";

  21:          this.<>1__state = 3;

  22:          return true;

  23:        }

  24:      case 2:

  25:        this.<>1__state = -1;

  26:        break;

  27:      case 3:

  28:        this.<>1__state = -1;

  29:        break;

  30:      case 4:

  31:        this.<>1__state = -1;

  32:        goto default;

  33:      default:

  34:        return false;

  35:    }

  36:    this.<>2__current = "Ignore";

  37:    this.<>1__state = 4;

  38:    return true;

  39:  }
Стало трохи запутаніше але зрозуміти логіку не так вже й важко.


Плюс до всього вище описаного, треба зрозуміти, що кожен мувнекст переходить на певний case і відповідно код який є в цьому case виконається лише тоді, коли ми дойдемо до нього, таким чином якщо ми б створювали якісь обєкти в одному з yield return то фактично отримуємо “ліниве завантаження” і відповідно програма працювала б ефективніше …


І останній приклад



   1:  

   2: public static IEnumerable<int> GetInt()

   3: {

   4:     for (int i = 0; i < 5; i++)

   5:     {

   6:         yield return i;

   7:  

   8:     }

   9: }

в результаті дасть нам :


   1: bool IEnumerator.MoveNext()

   2:   {

   3:     switch (this.<>1__state)

   4:     {

   5:       case 0:

   6:         this.<>1__state = -1;

   7:         this.<i>5__1 = 0;

   8:         break;

   9:       case 1:

  10:         this.<>1__state = -1;

  11:         ++this.<i>5__1;

  12:         break;

  13:       default:

  14:         return false;

  15:     }

  16:     if (this.<i>5__1 < 5)

  17:     {

  18:       this.<>2__current = this.<i>5__1;

  19:       this.<>1__state = 1;

  20:       return true;

  21:     }

  22:     else

  23:       goto default;

  24:   }

Отож підсумок, yield дає можливість модифікувати логіку методу MoveNext що є неможливим при роботі з простими колекціями. Те ж саме можна зробити реалізувавши всі необхідні інтерфейси вручну але це набагато довше.

Комментариев нет:

Отправить комментарий