您好,登錄后才能下訂單哦!
.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)該方法。
免責(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)容。