为什么IEnumerator接口没有继承IDisposable接口
泛型接口IEnumerator<T>继承了IDisposable接口
我们知道,泛型接口IEnumerator<T>继承了IDisposable接口,.Net为什么这么设计呢?因为foreach迭代时,迭代器可能使用了某些资源,例如,迭代器打开一个文件,然后迭代文件每一行。这就需要在迭代结束时释放这些资源,由于IEnumerator<T>继承了IDisposable接口,foreach语句可以编译为try{…} finally {enumerator.Dispose()}语句,在finally块中调用Dispose方法释放资源。网络上相关的文章很多,这里就不详细讲述。
为什么IEnumerator接口没有继承IDisposable接口
让我们奇怪的是,为什么非泛型接口IEnumerator没有实现IDisposable呢?使用IEnumerator,同样可能需要在迭代后释放资源,不是吗?于是我搜索到一篇提问<Why IEnumerator of T inherts from IDisposable, but non-generic IEnumerator does NOT?>,其中有个回答对这个问题做了非常好的解释,粘贴如下:
Basically it was an oversight. In C# 1.0, foreach
never called Dispose
. With C# 1.2 (introduced in VS2003 - there's no 1.1, bizarrely) foreach
began to check in the finally
block whether or not the iterator implemented IDisposable
- they had to do it that way, because retrospectively making IEnumerator
extend IDisposable
would have broken everyone's implementation of IEnumerator
. If they'd worked out that it's useful for foreach
to dispose of iterators in the first place, I'm sure IEnumerator
would have extended IDisposable
.
When C# 2.0 and .NET 2.0 came out, however, they had a fresh opportunity - new interface, new inheritance. It makes much more sense to have the interface extend IDisposable
so that you don't need an execution-time check in the finally block, and now the compiler knows that if the iterator is an IEnumerator<T>
it can emit an unconditional call to Dispose
.
EDIT: It's incredibly useful for Dispose
to be called at the end of iteration (however it ends). It means the iterator can hold on to resources - which makes it feasible for it to, say, read a file line by line. Iterator blocks generator Dispose
implementations which make sure that any finally
blocks relevant to the "current point of execution" of the iterator are executed when it's disposed - so you can write normal code within the iterator and clean-up should happen appropriately.
我翻译如下:
基本上,这是设计的失误。在C#1.0时,foreach是不会调用Dispose方法的。从C#1.2(VS2003采用此版本--奇怪的是,没有1.1版)开始,foreach 会在finally语句块中检查迭代器是否实现了IDisposable接口(来决定是否调用Dispose方法)--他们只能这么做,因为如果回溯设计IEnumerator扩展为支持IDisposable将会破坏原有的所有的对IEnumerator的实现。
然而,当C#2.0和.Net2.0到来时,他们得到了一个新的机会--新的接口,新的继承。这个接口(IEnumerator<T>)继承了IDisposable,这么做更加合理,这样你就不需要在运行时在finally block块中做检查了,现在编译器清楚的知道,如果迭代器实现了IEnumerator<T>,它就可以无条件地调用Dispose方法.
EDIT: 在迭代结束时调用Dispose方法是非常有用的。这意味着迭代器可以很好地管理资源--这将使得比如这样的事情--一行行地读取文件--变得可行。迭代代码块生成器生成的代码,将保证在迭代结束时任何和迭代器对应的finally块一定会执行。这样你只要在迭代器中编写一般的Dispose代码,(迭代时)清理工作就会正确地发生。
原来,IEnumerator没有继承IDisposable是微软的设计失误,为了弥补这个失误,C#1.2和之后的版本不得不在运行时检查迭代器是否实现了IDisposable接口来决定是否可以调用Dispose方法。注意,检查一定是在运行时进行的,因为在编译时,编译器是没有办法确定迭代器是否实现了IDisposable接口的。
自己用代码实现在finally块中检查迭代器是否实现IDisposable接口
根据我的理解,检查一定是利用反射做到的,下面我试图自己用代码来实现C#的这个workaround。(谢谢GoodSpeed的指正,通过Reflector阅读IL代码,我发现C#的实现和他指出的完全一样)
下面的Person和PeopleEnum类源自MSDN,我稍作了修改,使PeopleEnum继承IDispose接口。代码如下:
public class Person
{
public Person(string fName, string lName)
{
this.firstName = fName;
this.lastName = lName;
}
public string firstName;
public string lastName;
}
public class People : IEnumerable
{
private Person[] _people;
public People(Person[] pArray)
{
_people = new Person[pArray.Length];
for (int i = 0; i < pArray.Length; i++)
{
_people[i] = pArray[i];
}
}
public IEnumerator GetEnumerator()
{
return new PeopleEnum(_people);
}
}
public class PeopleEnum : IEnumerator,IDisposable
{
public Person[] _people;
// Enumerators are positioned before the first element
// until the first MoveNext() call.
int position = -1;
public PeopleEnum(Person[] list)
{
_people = list;
}
public bool MoveNext()
{
position++;
return (position < _people.Length);
}
public void Reset()
{
position = -1;
}
public object Current
{
get
{
try
{
return _people[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
public void Dispose()
{
Console.WriteLine("Dispose IEnumerator!");
}
}
使用foreach语句迭代:
Person[] peopleArray = new Person[3]
{
new Person("John", "Smith"),
new Person("Jim", "Johnson"),
new Person("Sue", "Rabon"),
};
People peopleList = new People(peopleArray);
foreach (Person p in peopleList)
Console.WriteLine(p.firstName + " " + p.lastName);
}
控制台输出为:
John Smith
Jim Johnson
Sue Rabon
Dispose IEnumerator!
Press any key to continue . . .
果然,Dispose方法被调用了,现在,我试着来自己来实现foreach语句的功能,代码如下
Person[] peopleArray = new Person[3]
{
new Person("John", "Smith"),
new Person("Jim", "Johnson"),
new Person("Sue", "Rabon"),
};
People peopleList = new People(peopleArray);
IEnumerator enumerator = peopleList.GetEnumerator();
enumerator.Reset();
try
{
while (enumerator.MoveNext())
{
Person p = (Person)enumerator.Current;
Console.WriteLine(p.firstName + " " + p.lastName);
}
}
finally
{
//Type type= enumerator.GetType();
// if (null != type.GetInterface("IDisposable"))
//{
// ((IDisposable)enumerator).Dispose();
//}
IDisposable disposable = enumerator as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
注意finally块中的代码,我们通过反射,检查enumerator是否支持IDisposable接口,如果支持,则调用Dispose方法,否则不做任何事情。
声明
本文为Binhua Liu原创作品。本文允许复制,修改,传递,但不允许用于商业用途。转载请注明出处。本文发表于2010年7月23日。