中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

.NET教程:.NET 面試題之IEnumerable(二)

發布時間:2020-06-17 15:12:54 來源:網絡 閱讀:396 作者:IT大贏家 欄目:編程語言

  .NET教程,這篇文章還是接著上文介紹的第二部分!多的不說,直接獻上內容!

      使用yield關鍵字實現方法GetEnumerator

  如果iterator本身有實現IEnumerator接口(本例就是一個數組),則可以有更容易的方法:

  public IEnumerator GetEnumerator()

  {

  return _people.GetEnumerator();

  }

  注意,這個方法沒有Foreach的存在,所以如果你改用for循環去迭代這個集合,你得自己去呼叫MoveNext,然后獲得集合的下一個成員。而且會出現一個問題,就是你無法知道集合的大小(IEnumerable沒有Count方法,只有IEnumerable才有)。

  此時,可以做個試驗,如果我們知道一個集合有3個成員,故意迭代多幾次,比如迭代10次,那么當集合已經到達尾部時,將會拋出InvalidOperationException異常。

  class Program

  {

  static void Main(string[] args)

  {

  Person p1 = new Person("1");

  Person p2 = new Person("2");

  Person p3 = new Person("3");

  People p = new People(new Person[3]{p1, p2, p3});

  var enumerator = p.GetEnumerator();

  //Will throw InvalidOperationException

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

  {

  enumerator.MoveNext();

  if (enumerator.Current != null)

  {

  var currentP = (Person) enumerator.Current;

  Console.WriteLine("current is {0}", currentP.Name);

  }

  }

  Console.ReadKey();

  }

  }

  public class Person

  {

  public string Name { get; set; }

  public Person(string name)

  {

  Name = name;

  }

  }

  public class People : IEnumerable

  {

  private readonly Person[] _persons;

  public People(Person[] persons)

  {

  _persons = persons;

  }

  public IEnumerator GetEnumerator()

  {

  return _persons.GetEnumerator();

  }

  }

  使用yield關鍵字配合return,編譯器將會自動實現繼承IEnumerator接口的類和上面的三個方法。而且,當for循環遍歷超過集合大小時,不會拋出異常,Current會一直停留在集合的最后一個元素。

  public IEnumerator GetEnumerator()

  {

  foreach (Person p in _people)

  yield return p;

  }

  如果我們在yield的上面加一句:

  public IEnumerator GetEnumerator()

  {

  foreach (var p in _persons)

  {

  Console.WriteLine("test");

  yield return p;

  }

  }

  我們會發現test只會打印三次。后面因為已經沒有新的元素了,yield也就不執行了,整個Foreach循環將什么都不做。

  yield的延遲執行特性 – 本質上是一個狀態機

  關鍵字yield只有當真正需要迭代并取到元素時才會執行。yield是一個語法糖,它的本質是為我們實現IEnumerator接口。

  static void Main(string[] args)

  {

  IEnumerable items = GetItems();

  Console.WriteLine("Begin to iterate the collection.");

  var ret = items.ToList();

  Console.ReadKey();

  }

  static IEnumerable GetItems()

  {

  Console.WriteLine("Begin to invoke GetItems()");

  yield return "1";

  yield return "2";

  yield return "3";

  }

  在上面的例子中,盡管我們呼叫了GetItems方法,先打印出來的句子卻是主函數中的句子。這是因為只有在ToList時,才真正開始進行迭代,獲得迭代的成員。我們可以使用ILSpy察看編譯后的程序集的內容,并在View -> Option的Decompiler中,關閉所有的功能對勾(否則你將仍然只看到一些yield),然后檢查Program類型,我們會發現編譯器幫我們實現的MoveNext函數,實際上是一個switch。第一個yield之前的所有代碼,統統被放在了第一個case中。

  bool IEnumerator.MoveNext()

  {

  bool result;

  switch (this.<>1__state)

  {

  case 0:

  this.<>1__state = -1;

  Console.WriteLine("Begin to invoke GetItems()");

  this.<>2__current = "1";

  this.<>1__state = 1;

  result = true;

  return result;

  case 1:

  this.<>1__state = -1;

  this.<>2__current = "2";

  this.<>1__state = 2;

  result = true;

  return result;

  case 2:

  this.<>1__state = -1;

  this.<>2__current = "3";

  this.<>1__state = 3;

  result = true;

  return result;

  case 3:

  this.<>1__state = -1;

  break;

  }

  result = false;

  return result;

  }

  如果某個yield之前有其他代碼,它會自動包容到它最近的后續的yield的“統治范圍”:

  static IEnumerable GetItems()

  {

  Console.WriteLine("Begin to invoke GetItems()");

  Console.WriteLine("Begin to invoke GetItems()");

  yield return "1";

  Console.WriteLine("Begin to invoke GetItems()");

  yield return "2";

  Console.WriteLine("Begin to invoke GetItems()");

  Console.WriteLine("Begin to invoke GetItems()");

  Console.WriteLine("Begin to invoke GetItems()");

  yield return "3";

  }

  它的編譯結果也是可以預測的:

  case 0:

  this.<>1__state = -1;

  Console.WriteLine("Begin to invoke GetItems()");

  Console.WriteLine("Begin to invoke GetItems()");

  this.<>2__current = "1";

  this.<>1__state = 1;

  result = true;

  return result;

  case 1:

  this.<>1__state = -1;

  Console.WriteLine("Begin to invoke GetItems()");

  this.<>2__current = "2";

  this.<>1__state = 2;

  result = true;

  return result;

  case 2:

  this.<>1__state = -1;

  Console.WriteLine("Begin to invoke GetItems()");

  Console.WriteLine("Begin to invoke GetItems()");

  Console.WriteLine("Begin to invoke GetItems()");

  this.<>2__current = "3";

  this.<>1__state = 3;

  result = true;

  return result;

  case 3:

  this.<>1__state = -1;

  break;

  這也就解釋了為什么第一個打印出來的句子在主函數中,因為所有不是yield的代碼統統都被yield吃掉了,并成為狀態機的一部分。而在迭×××始之前,代碼是無法運行到switch分支的。

  令人矚目的是,編譯器沒有實現reset方法,這意味著不支持多次迭代:

  void IEnumerator.Reset()

  {

  throw new NotSupportedException();

  }

  yield只返回,不賦值

  下面這個例子。不過我認為Artech大大分析的不是很好,我給出自己的解釋。

  class Program

  {

  static void Main(string[] args)

  {

  IEnumerable vectors = GetVectors();

  //Begin to call GetVectors

  foreach (var vector in vectors)

  {

  vector.X = 4;

  vector.Y = 4;

  }

  //Before this iterate, there are 3 members in vectors, all with X and Y = 4

  foreach (var vector in vectors)

  {

  //But this iterate will change the value of X and Y BACK to 1/2/3

  Console.WriteLine(vector);

  }

  }

  static IEnumerable GetVectors()

  {

  yield return new Vector(1, 1);

  yield return new Vector(2, 3);

  yield return new Vector(3, 3);

  }

  }

  public class Vector

  {

  public double X { get; set; }

  public double Y { get; set; }

  public Vector(double x, double y)

  {

  this.X = x;

  this.Y = y;

  }

  public override string ToString()

  {

  return string.Format("X = {0}, Y = {1}", this.X, this.Y);

  }

  }

  我們進行調試,并將斷點設置在第二次迭代之前,此時,我們發現vector的值確實變成4了,但第二次迭代之后,值又回去了,好像被改回來了一樣。但實際上,并沒有改任何值,yield只是老老實實的吐出了新的三個vector而已。Yield就像一個血汗工廠,不停的制造新值,不會修改任何值。

  從編譯后的代碼我們發現,只要我們通過foreach迭代一個IEnumerable,我們就會跑到GetVectors方法中,而每次運行GetVectors方法,yield都只會返回全新的三個值為(1,1),(2,2)和(3,3)的vector,仿佛第一次迭代完全沒有運行過一樣。原文中,也有實驗證明了vector創建了六次,實際上每次迭代都會創建三個新的vector。

  解決這個問題的方法是將IEnumerable轉為其子類型例如List或數組。

  在迭代的過程中改變集合的狀態

  foreach迭代時不能直接更改集合成員的值,但如果集合成員是類或者結構,則可以更改其屬性或字段的值。不能在為集合刪除或者增加成員,這會出現運行時異常。For循環則可以。

  var vectors = GetVectors().ToList();

  foreach (var vector in vectors)

  {

  if (vector.X == 1)

  //Error

  //vectors.Remove(vector);

  //This is OK

  vector.X = 99;

  Console.WriteLine(vector);

  }

  IEnumerable的缺點

  IEnumerable功能有限,不能插入和刪除。

  訪問IEnumerable只能通過迭代,不能使用索引器。迭代顯然是非線程安全的,每次IEnumerable都會生成新的IEnumerator,從而形成多個互相不影響的迭代過程。

  在迭代時,只能前進不能后退。新的迭代不會記得之前迭代后值的任何變化。


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

双牌县| 永康市| 金华市| 宜良县| 连南| 历史| 沙河市| 九龙城区| 眉山市| 新化县| 汤原县| 绥滨县| 唐海县| 永新县| 黔东| 河间市| 鄢陵县| 新竹市| 桦甸市| 鹤山市| 资中县| 姚安县| 察隅县| 湟源县| 错那县| 巴中市| 东乡县| 平潭县| 印江| 汝阳县| 二连浩特市| 呼玛县| 永胜县| 武义县| 曲麻莱县| 明光市| 武宁县| 紫阳县| 同江市| 永福县| 德阳市|