分享|C# 中yield關鍵字解析

  • 2019 年 11 月 7 日
  • 筆記

本文長度為4830,預計閱讀13分鐘

前言

  前段時間了解到yield關鍵字,一直覺得還不錯。今天給大家分享一下yield關鍵字的用法。yield return 返回集合不是一次性返回所有集合元素,而是一次調用返回一個元素。具體如何使用yield return 返回集合呢?我們一起往下面看吧。。

yield使用介紹

yield return 和yield break:

我們看下平常循環返回集合的使用操作(返回1-100中的偶數):

  class Program      {          static private List<int> _numArray; //用來保存1-100 這100個整數            Program() //構造函數。我們可以通過這個構造函數往待測試集合中存入1-100這100個測試數據          {              _numArray = new List<int>(); //給集合變數開始在堆記憶體上開記憶體,並且把記憶體首地址交給這個_numArray變數                for (int i = 1; i <= 100; i++)              {                  _numArray.Add(i);  //把1到100保存在集合當中方便操作              }          }            static void Main(string[] args)          {              new Program();                TestMethod();              }            //測試求1到100之間的全部偶數          static public void TestMethod()          {              foreach (var item in GetAllEvenNumberOld())              {                  Console.WriteLine(item); //輸出偶數測試              }          }            /// <summary>          /// 使用平常返回集合方法          /// </summary>          /// <returns></returns>          static IEnumerable<int> GetAllEvenNumberOld()          {              var listNum = new List<int>();              foreach (int num in _numArray)              {                  if (num % 2 == 0) //判斷是不是偶數                  {                      listNum.Add(num); //返回當前偶數                    }              }              return listNum;          }      }

然後我們再看看使用yield return返回集合操作:

 class Program      {          static private List<int> _numArray; //用來保存1-100 這100個整數            Program() //構造函數。我們可以通過這個構造函數往待測試集合中存入1-100這100個測試數據          {              _numArray = new List<int>(); //給集合變數開始在堆記憶體上開記憶體,並且把記憶體首地址交給這個_numArray變數                for (int i = 1; i <= 100; i++)              {                  _numArray.Add(i);  //把1到100保存在集合當中方便操作              }          }            static void Main(string[] args)          {              new Program();                TestMethod();              }            //測試求1到100之間的全部偶數          static public void TestMethod()          {              foreach (var item in GetAllEvenNumber())              {                  Console.WriteLine(item); //輸出偶數測試              }          }            //使用Yield Return情況下的方法          static IEnumerable<int> GetAllEvenNumber()          {                foreach (int num in _numArray)              {                  if (num % 2 == 0) //判斷是不是偶數                  {                      yield return num; //返回當前偶數                    }              }              yield break;  //當前集合已經遍歷完畢,我們就跳出當前函數,其實你不加也可以              //這個作用就是提前結束當前函數,就是說這個函數運行完畢了。          }      }

與平常return比較

上面我們看到了yield return 的使用方法,那麼這個與return返回集合有什麼區別呢?我們看下面一個案例來進行分析:

我們首先先看通過returun返回集合的一個案例:

    class Program      {          static void Main(string[] args)          {              foreach (var item in GetNums())              {                  Console.WriteLine($"  common return:{item}");              }          }            /// <summary>          /// 平常return 返回集合          /// </summary>          /// <returns></returns>          public static IEnumerable<int> GetNums()          {              var listNum = new List<int>();              for (int i = 0; i < 10; i++)              {                  Console.WriteLine($"yield return:{i}");                  listNum.Add(i);              }              return listNum;          }      }

通過程式碼的運行結果,我們可以看到這裡返回的結果 yield return 和comment return是分成兩邊的。先執行完一個然後開始執行另外一個。不干涉。

我們接著看下使用yield return返回集合:

    class Program      {          static void Main(string[] args)          {              foreach (var item in GetNumsYield())              {                  Console.WriteLine($"  common return:{item}");              }          }            /// <summary>          /// 通過yield return 返回集合          /// </summary>          /// <returns></returns>          public static IEnumerable<int> GetNumsYield()          {              for (int i = 0; i < 10; i++)              {                  Console.WriteLine($"yield return:{i}");                  yield return i;              }          }      }

我們看這個運行結果,這裡yield return 和comment return 的輸出完全交替了。這裡說明是一次調用就返回了一個元素。

通過上面的案例我們可以發現,yield return 並不是等所有執行完了才一次性返回的。而是調用一次就返回一次結果的元素。這也就是按需供給。

解析定義類

我們已經大致了解了yield 的用法和它與平常的返回的區別。我們可以繼續查看其運行原理。我們首先看這麼一個案例(在0-10中隨機返回五個數字):

我們通過SharpLab反編譯其程式碼,我們進行查看發現yield具體詳細實現:

我們看到yield內部含有一個迭代器。這樣去實現的迭代遍歷。同時包含_state欄位、用來存儲上一次的記錄。_current包含當前的值、也通過_initialThreadId獲取當前執行緒id。其中主要的方法是迭代器方法MoveNext()。我們根據反編譯結果來實現一個與yiled相似的類:

    /// <summary>      /// 解析yield並定義相似類      /// </summary>      public sealed class GetRandomNumbersClass : IEnumerable<int>, IEnumerable, IEnumerator<int>, IDisposable, IEnumerator      {          public static Random r = new Random();            /// <summary>          /// 狀態          /// </summary>          private int _state;            /// <summary>          ///儲存當前值          /// </summary>          private int _current;            /// <summary>          /// 執行緒id          /// </summary>          private int _initialThreadId;            /// <summary>          /// 集合元素數量          /// </summary>          private int count;            /// <summary>          /// 集合元素數量          /// </summary>          public int _count;            /// <summary>          /// 當前指針          /// </summary>          private int i;            int IEnumerator<int>.Current          {              [DebuggerHidden]              get              {                  return _current;              }          }            object IEnumerator.Current          {              [DebuggerHidden]              get              {                  return _current;              }          }            [DebuggerHidden]          public GetRandomNumbersClass(int state)  {              this._state = state;              _initialThreadId = Environment.CurrentManagedThreadId;          }            [DebuggerHidden]          void IDisposable.Dispose()          {          }            private bool MoveNext()  {              switch (_state)              {                  default:                      return false;                  case 0:                      _state = -1;                      i = 0;                      break;                  case 1:                      _state = -1;                      i++;                      break;              }              if (i < count)              {                  _current = r.Next(10);                  _state = 1;                  return true;              }              return false;          }            bool IEnumerator.MoveNext()          {              //ILSpy generated this explicit interface implementation from .override directive in MoveNext              return this.MoveNext();          }            [DebuggerHidden]          void IEnumerator.Reset()          {              throw new NotSupportedException();          }            [DebuggerHidden]          public IEnumerator<int> GetEnumerator()          {              GetRandomNumbersClass _getRandom;              if (_state == -2 && _initialThreadId == Environment.CurrentManagedThreadId)              {                  _state = 0;                  _getRandom = this;              }              else              {                  _getRandom = new GetRandomNumbersClass(0);              }              _getRandom.count = _count;              return _getRandom;          }            [DebuggerHidden]          IEnumerator IEnumerable.GetEnumerator()          {              return GetEnumerator();          }              [IteratorStateMachine(typeof(GetRandomNumbersClass))]          private static IEnumerable<int> GetList(int count)          {              GetRandomNumbersClass getRandomNumbersClass = new GetRandomNumbersClass(-2);              getRandomNumbersClass._count = count;              return getRandomNumbersClass;          }          private static void Main(string[] args)  {              IEnumerator<int> enumerator = GetList(5).GetEnumerator();              try              {                  foreach (int item in GetList(5))                      Console.WriteLine(item);                  //while (enumerator.MoveNext())                  //{                  //    int current = enumerator.Current;                  //    Console.WriteLine(current);                  //}              }              finally              {                  if (enumerator != null)                  {                      enumerator.Dispose();                  }              }              Console.ReadKey();          }      }