调用者没有"完成"的'yield'枚举 - 会发生什么

pm1*_*100 36 c# yield-return

假设我有

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方法.

  • 如果有疑问写一个测试程序:-) ty (4认同)