C#确保迭代器方法正常完成

本文关键字:常完成 方法 迭代器 确保 | 更新日期: 2023-09-27 18:20:41

我测试了这段代码,发现GetInts方法并没有像我传统上预期的那样退出该方法并打印"GetInts disconnected"。我想写一个滚动控件,以yield return从数据库中增量下载数据行,但我不确定正确的方法。

另一方面,用using块包装yield return块将保证对dispose()的调用,但我应该这样做吗?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace IteratorPattern
{
    class Program
    {
        static IEnumerator<int> _mIter;
        static void Main(string[] args)
        {
            // get an enumerator to enumerate values in the database, at a later time
            _mIter = GetInts(100).GetEnumerator();
            // simulate some scrolling, which will add values to my scroll box
            Scroll(10);
            // suppose this control is now redundant,
            // but this does not disconnect the data source 
            _mIter.Dispose();
            // program ends will connection still open?
            Console.WriteLine("Program End");
        }
        // iterate and cache (not implemented) values
        private static void Scroll(int units)
        {
            Console.WriteLine("Scroll()");
            while(units-- != 0 && _mIter.MoveNext())
            {
                Console.WriteLine(_mIter.Current);
            }
            Console.WriteLine("Scroll() completed");
        }
        // connect to database, yield-return each datarow, and disconnect (hopefully)
        static IEnumerable<int> GetInts(int i)
        {
            Console.WriteLine("GetInts connected");
            using (var ds = new DataSourceWrapper())
            {
                while (i-- != 0)
                {
                    Console.WriteLine("yield {0}", i);
                    yield return i;
                }
            }
            // not called! 
            Console.WriteLine("GetInts disconnected");
        }
    }
    // try using a datasource wrapper to ensure Dispose() is called to disconnect the connection.
    public class DataSourceWrapper : IDisposable
    {
        public void Dispose()
        {
            Console.WriteLine("DataSource Disconnected");
        }
    }
}

C#确保迭代器方法正常完成

当我运行它时,它断开:

Scroll()
GetInts connected
yield 99
99
...snip...
yield 90
90
Scroll() completed
DataSource Disconnected
Program End

请注意,如果您正在读取数据的末尾,它断开本身;然而,您要求100 int,并滚动其中的10 int;就迭代器而言,你让它挂起了10%。如果你耗尽了一个迭代器块,它会清除任何using等;如果你没有耗尽它,就需要明确地处理它。您可以通过将其更改为GetInts(5)(使所有其他代码保持不变)来说明这一点:

Scroll()
GetInts connected
yield 4
4
yield 3
3
yield 2
2
yield 1
1
yield 0
0
DataSource Disconnected
GetInts disconnected
Scroll() completed
Program End

它没有显示"GetInts disconnected"的原因是。。。除非你耗尽它,否则它永远不会到达那里!这就是您的代码所说的:"打印已连接;生成i项;打印已断开连接"——除非它首先完成所有的生成,否则它不会打印已断开连接。如果您使用finally(再次使用GetInts(100)),则此项更改为:

    static IEnumerable<int> GetInts(int i)
    {
        Console.WriteLine("GetInts connected");
        try
        {
            using (var ds = new DataSourceWrapper())
            {
                while (i-- != 0)
                {
                    Console.WriteLine("yield {0}", i);
                    yield return i;
                }
            }
        }
        finally
        {
            // not called! 
            Console.WriteLine("GetInts disconnected");
        }
    }

然后它就工作了:

...
yield 90
90
Scroll() completed
DataSource Disconnected
GetInts disconnected
Program End

基本上,finally被映射到迭代器的Dispose()中。

此外,_mIterIEnumerator<T>;它显式地实现IDisposable,并且您负责在某个时刻调用Dispose()。这样做,一切都会成功的。即使使用IEnumerator(非泛型,不是显式IDisposable),也应该遵循与编译器相同的方法,检查枚举器是否为IDisposable,并相应地调用Dispose()

由于您不是在一个区块中使用数据,因此不能使用using,但仍必须对其进行处理。这可能意味着使您的类型实现IDisposable,并传递调用。此外,您可能希望在到达末尾时显式处理并释放枚举器。我真的无法更改您的代码来说明这一点,因为它在static数据上没有意义,但我希望枚举器在实际代码上无论如何都不会是static

您声称它没有"断开数据库连接",但您显示的代码确实打印出了"DataSource disconnected"。。。所以就我所见,它运行得很好。

是的,您需要确保有东西在迭代器上调用Dispose——这通常应该用using语句或(更常见的)用foreach循环迭代IEnumerable来完成。

您不能保证迭代器块会自己运行到完成,但当迭代器被释放时,任何适当的finally都将执行。

编辑:所以,如果你想确保你会看到"GetInts disconnected",你可以把它放在finally块中:

static IEnumerable<int> GetInts(int i)
{
    try
    {
        Console.WriteLine("GetInts connected");
        using (var ds = new DataSourceWrapper())
        {
            while (i-- != 0)
            {
                Console.WriteLine("yield {0}", i);
                yield return i;
            }
        }
    }
    finally
    {
        Console.WriteLine("GetInts disconnected");
    }
}