假设我有
IEnumerable<string> Foo()
{
try
{
/// open a network connection, start reading packets
while(moredata)
{
yield return packet;
}
}
finally
{
// close connection
}
}
Run Code Online (Sandbox Code Playgroud)
(或许我做了'使用' - 同样的事情).如果我的来电者去了会怎么样
var packet = Foo().First();
Run Code Online (Sandbox Code Playgroud)
我刚刚离开了泄漏的连接.什么时候最终被调用?或者正确的事情总是通过魔法发生
用答案和想法编辑
我的示例和其他'普通'(foreach,..)调用模式将很好地工作,因为它们处理IEnumerable(实际上是GetEnumerator返回的IEnumerator).因此,我必须在某个地方设置一个可以做一些时髦的事情(明确地获取一个枚举器而不是处理它等).我会让他们开枪
糟糕的代码
我找到了一个来电者
IEnumerator<T> enumerator = foo().GetEnumerator();
Run Code Online (Sandbox Code Playgroud)
变成
using(IEnumerator<T> enumerator = foo().GetEnumerator())
Run Code Online (Sandbox Code Playgroud)
Ser*_*rvy 37
我刚刚离开了泄漏的连接.
不你不是.
什么时候最终被调用?
当IEnumerator<T>被处理时,这First将在获得序列的第一项之后进行(就像每个人在使用时都应该这样做IEnumerator<T>).
现在如果有人写道:
//note no `using` block on `iterator`
var iterator = Foo().GetEnumerator();
iterator.MoveNext();
var first = iterator.Current;
//note no disposal of iterator
Run Code Online (Sandbox Code Playgroud)
然后他们会泄漏资源,但是错误在调用者代码中,而不是迭代器块.
das*_*ght 27
你最终不会泄漏连接.由yield returnare IDisposable和LINQ函数生成的迭代器对象小心确保正确处理.
例如,First()实现如下:
public static TSource First<TSource>(this IEnumerable<TSource> source) {
if (source == null) throw Error.ArgumentNull("source");
IList<TSource> list = source as IList<TSource>;
if (list != null) {
if (list.Count > 0) return list[0];
}
else {
using (IEnumerator<TSource> e = source.GetEnumerator()) {
if (e.MoveNext()) return e.Current;
}
}
throw Error.NoElements();
}
Run Code Online (Sandbox Code Playgroud)
注意如何source.GetEnumerator()包装结果using.这样可以确保调用Dispose,从而确保在finally块中调用代码.
foreach循环迭代也是如此:代码确保枚举器的处理,无论枚举是否完成.
唯一可能导致泄露连接的情况是,当您打电话给GetEnumerator自己时,未能正确处理它.但是,这在代码使用中是错误的IEnumerable,而不是在IEnumerable本身.
the*_*heB 22
好的,这个问题可以使用一些经验数据.
使用VS2015和临时项目,我编写了以下代码:
private IEnumerable<string> Test()
{
using (TestClass t = new TestClass())
{
try
{
System.Diagnostics.Debug.Print("1");
yield return "1";
System.Diagnostics.Debug.Print("2");
yield return "2";
System.Diagnostics.Debug.Print("3");
yield return "3";
System.Diagnostics.Debug.Print("4");
yield return "4";
}
finally
{
System.Diagnostics.Debug.Print("Finally");
}
}
}
private class TestClass : IDisposable
{
public void Dispose()
{
System.Diagnostics.Debug.Print("Disposed");
}
}
Run Code Online (Sandbox Code Playgroud)
然后称它为两种方式:
foreach (string s in Test())
{
System.Diagnostics.Debug.Print(s);
if (s == "3") break;
}
string f = Test().First();
Run Code Online (Sandbox Code Playgroud)
这会产生以下调试输出
1
1
2
2
3
3
Finally
Disposed
1
Finally
Disposed
Run Code Online (Sandbox Code Playgroud)
我们可以看到,它执行finally块和Dispose方法.