溫馨提示×

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

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

.NET教程:.NET 面試題之IEnumerable

發(fā)布時(shí)間:2020-06-17 08:40:54 來(lái)源:網(wǎng)絡(luò) 閱讀:409 作者:IT大贏家 欄目:編程語(yǔ)言

  .NET教程,今天給大家介紹的是:.NET 面試題之IEnumerable ,這是在面試的時(shí)候可能會(huì)碰到的一道題目,這道題的注解分為了兩個(gè)部分,這一篇是第一部分!

  什么是IEnumerable?

  IEnumerable及IEnumerable的泛型版本IEnumerable是一個(gè)接口,它只含有一個(gè)方法GetEnumerator。Enumerable這個(gè)靜態(tài)類型含有很多擴(kuò)展方法,其擴(kuò)展的目標(biāo)是IEnumerable。

  實(shí)現(xiàn)了這個(gè)接口的類可以使用Foreach關(guān)鍵字進(jìn)行迭代(迭代的意思是對(duì)于一個(gè)集合,可以逐一取出元素并遍歷之)。

  實(shí)現(xiàn)這個(gè)接口必須實(shí)現(xiàn)方法GetEnumerator。

  如何實(shí)現(xiàn)一個(gè)繼承IEnumerable的類型?

  實(shí)現(xiàn)一個(gè)繼承IEnumerable的類型等同于實(shí)現(xiàn)方法GetEnumerator。想知道如何實(shí)現(xiàn)方法GetEnumerator,不妨思考下實(shí)現(xiàn)了GetEnumerator之后的類型在Foreach之下的行為:

  可以獲得第一個(gè)或當(dāng)前成員

  可以移動(dòng)到下一個(gè)成員

  可以在集合沒(méi)有下一個(gè)成員時(shí)退出循環(huán)。

  假設(shè)我們有一個(gè)很簡(jiǎn)單的Person類(例子來(lái)自MSDN):

  public class Person

  {

  public Person(string fName, string lName)

  {

  FirstName = fName;

  LastName = lName;

  }

  public string FirstName;

  public string LastName;

  }

  然后我們想構(gòu)造一個(gè)沒(méi)有實(shí)現(xiàn)IEnumerable的類型,其儲(chǔ)存多個(gè)Person,然后再對(duì)這個(gè)類型實(shí)現(xiàn)IEnumerable。

  這個(gè)類型實(shí)際上的作用就相當(dāng)于Person[]或List,但我們不能使用它們,因?yàn)樗鼈円呀?jīng)實(shí)現(xiàn)了IEnumerable,故我們構(gòu)造一個(gè)People類,模擬很多人(People是Person的復(fù)數(shù)形式)。

  這個(gè)類型允許我們傳入一組Person的數(shù)組。所以它應(yīng)當(dāng)有一個(gè)Person[]類型的成員,和一個(gè)構(gòu)造函數(shù),其可以接受一個(gè)Person[],然后將Person[]類型的成員填充進(jìn)去作為初始化。

  //People類就是Person類的集合

  //但我們不能用List或者Person[],因?yàn)樗麄兌紝?shí)現(xiàn)了IEnumerable

  //我們要自己實(shí)現(xiàn)一個(gè)IEnumerable

  //所以請(qǐng)將People類想象成List或者類似物

  public class People : IEnumerable

  {

  private readonly Person[] _people;

  public People(Person[] pArray)

  {

  //構(gòu)造一個(gè)Person的集合

  _people = new Person[pArray.Length];

  for (var i = 0; i < pArray.Length; i++)

  {

  _people[i] = pArray[i];

  }

  }

  //實(shí)現(xiàn)IEnumerable需要實(shí)現(xiàn)GetEnumerator方法

  public IEnumerator GetEnumerator()

  {

  throw new NotImplementedException();

  }

  }

  我們的主函數(shù)應(yīng)當(dāng)是:

  public static void Main(string[] args)

  {

  //新的Person數(shù)組

  Person[] peopleArray =

  {

  new Person("John", "Smith"),

  new Person("Jim", "Johnson"),

  new Person("Sue", "Rabon"),

  };

  //People類實(shí)現(xiàn)了IEnumerable

  var peopleList = new People(peopleArray);

  //枚舉時(shí)先訪問(wèn)MoveNext方法

  //如果返回真,則獲得當(dāng)前對(duì)象,返回假,就退出此次枚舉

  foreach (Person p in peopleList)

  Console.WriteLine(p.FirstName + " " + p.LastName);

  }

  但現(xiàn)在我們的程序不能運(yùn)行,因?yàn)槲覀冞€沒(méi)實(shí)現(xiàn)GetEnumerator方法。

  實(shí)現(xiàn)方法GetEnumerator

  GetEnumerator方法需要一個(gè)IEnumerator類型的返回值,這個(gè)類型是一個(gè)接口,所以我們不能這樣寫:

  return new IEnumerator();

  因?yàn)槲覀儾荒軐?shí)例化一個(gè)接口。我們必須再寫一個(gè)類PeopleEnumerator,它繼承這個(gè)接口,實(shí)現(xiàn)這個(gè)接口所有的成員:Current屬性,兩個(gè)方法MoveNext和Reset。于是我們的代碼又變成了這樣:

  //實(shí)現(xiàn)IEnumerable需要實(shí)現(xiàn)GetEnumerator方法

  public IEnumerator GetEnumerator()

  {

  return new PeopleEnumerator();

  }

  在類型中:

  public class PeopleEnumerator : IEnumerator

  {

  public bool MoveNext()

  {

  throw new NotImplementedException();

  }

  public void Reset()

  {

  throw new NotImplementedException();

  }

  public object Current { get; }

  }

  現(xiàn)在問(wèn)題轉(zhuǎn)移為實(shí)現(xiàn)兩個(gè)方法,它們的功能看上去一目了然:一個(gè)負(fù)責(zé)將集合中Current向后移動(dòng)一位,一個(gè)則將Current初始化為0。

  我們可以查看IEnumerator元數(shù)據(jù),其解釋十分清楚:

  Enumerator代表一個(gè)類似箭頭的東西,它指向這個(gè)集合當(dāng)前迭代指向的成員

  IEnumerator接口類型對(duì)非泛型集合實(shí)現(xiàn)迭代

  Current表示集合當(dāng)前的元素,我們需要用它僅有的get方法取得當(dāng)前元素

  MoveNext方法根據(jù)Enumerator是否可以繼續(xù)向后移動(dòng)返回真或假

  Reset方法將Enumerator移到集合的開(kāi)頭

  通過(guò)上面的文字,我們可以理解GetEnumerator方法,就是獲得當(dāng)前Enumerator指向的成員。我們引入一個(gè)整型變量position來(lái)記錄當(dāng)前的位置,并且先試著寫下:

  public class PeopleEnumerator : IEnumerator

  {

  public Person[] _peoples;

  public object Current { get; }

  //當(dāng)前位置

  public int position;

  //構(gòu)造函數(shù)接受外部一個(gè)集合并初始化自己內(nèi)部的屬性_peoples

  public PeopleEnumerator(Person[] peoples)

  {

  _peoples = peoples;

  }

  //如果沒(méi)到集合的尾部就移動(dòng)position,返回一個(gè)bool

  public bool MoveNext()

  {

  if (position < _peoples.Length)

  {

  position++;

  return true;

  }

  return false;

  }

  public void Reset()

  {

  position = 0;

  }

  }

  這看上去好像沒(méi)問(wèn)題,但一執(zhí)行之后卻發(fā)現(xiàn):

  當(dāng)執(zhí)行到MoveNext方法時(shí),position會(huì)先增加1,這導(dǎo)致第一個(gè)元素(在位置0)會(huì)被遺漏,故position的初始值應(yīng)當(dāng)為-1而不是0

  當(dāng)前位置變量position顯然應(yīng)該是私有的

  需要編寫Current屬性的get方法取出當(dāng)前位置(position)上的集合成員

  通過(guò)不斷的調(diào)試,最后完整的實(shí)現(xiàn)應(yīng)當(dāng)是:

  public class PeopleEnumerator : IEnumerator

  {

  public Person[] People;

  //每次運(yùn)行到MoveNext或Reset時(shí),利用get方法自動(dòng)更新當(dāng)前位置指向的對(duì)象

  object IEnumerator.Current

  {

  get

  {

  try

  {

  //當(dāng)前位置的對(duì)象

  return People[_position];

  }

  catch (IndexOutOfRangeException)

  {

  throw new InvalidOperationException();

  }

  }

  }

  //當(dāng)前位置

  private int _position = -1;

  public PeopleEnumerator(Person[] people)

  {

  People = people;

  }

  //當(dāng)程序運(yùn)行到foreach循環(huán)中的in時(shí),就調(diào)用這個(gè)方法獲得下一個(gè)person對(duì)象

  public bool MoveNext()

  {

  _position++;

  //返回一個(gè)布爾值,如果為真,則說(shuō)明枚舉沒(méi)有結(jié)束。

  //如果為假,說(shuō)明已經(jīng)到集合的結(jié)尾,就結(jié)束此次枚舉

  return (_position < People.Length);

  }

  public void Reset() => _position = -1;

  }

  為什么當(dāng)程序運(yùn)行到in時(shí),會(huì)呼叫方法MoveNext呢?我們并沒(méi)有直接調(diào)用這個(gè)方法啊?當(dāng)你試圖查詢IL時(shí),就會(huì)得到答案。實(shí)際上下面兩段代碼的作用是相同的:

  foreach (T item in collection)

  {

  ...

  }

  IEnumerator enumerator = collection.GetEnumerator();

  while (enumerator.MoveNext())

  {

  T item = enumerator.Current;

  ...

  }

  注意:第二段代碼中,沒(méi)有呼叫Reset方法,也不需要呼叫。當(dāng)你呼叫時(shí),你會(huì)得到一個(gè)異常,這是因?yàn)榫幾g器沒(méi)有實(shí)現(xiàn)該方法。



向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI