是否可以为yield-return 方法提供一个“finally”代码块?

Hel*_*iac 3 c# ienumerable yield-return do-while

背景

大家好。我有一个名为 it 的抽象类,BaseRecordFetcher<TEntity>它有一个方法,该方法从子类中获取 read/sort/translate/move-next 方法,并yield 将结果作为模型实体返回。

当我读取多个数据行时,它正确地yield return为每个实体执行 a 操作,然后在没有任何问题的情况下到达跟踪消息do...while

问题

然而我注意到,当我IEnumerable.FirstOrDefault()在集合上使用时,系统假设不需要更多的收益返回,并且它不会完成此方法的执行

除了返回多少记录的跟踪输出之外,我对这种行为没有太大问题,但它让我思考:“如果我确实需要一些......让我们称之为代码怎么办?finally

问题

有没有办法始终确保系统在之后运行一些后处理代码yield return

代码

/// <summary>
///     Retrieve translated entities from the database. The methods used to do
///     this are specified from the child class as parameters (i.e. Action or
///     Func delegates).
/// </summary>
/// <param name="loadSubsetFunc">
///     Specify how to load a set of database records. Return boolean
///     confirmation that records were found.
/// </param>
/// <param name="preIterationAction">
///     Specify what should happen to sort the results.
/// </param>
/// <param name="translateRowFunc">
///     Specify how a database record should translate to a model entity.
///     Return the new entity.
/// </param>
/// <param name="moveNextFunc">
///     Specify how the database row pointer should move on. Return FALSE on a
///     call to the final row.
/// </param>
/// <returns>
///     A set of translated entities from the database.
/// </returns>
/// <example><code>
///
/// return base.FetchRecords(
///     _dOOdad.LoadFacilitySites,
///     () => _dOOdad.Sort = _dOOdad.GetAutoKeyColumn(),
///     () => 
///         {
///             var entity = new FacilitySite();
///             return entity.PopulateLookupEntity(
///                 _dOOdad.CurrentRow.ItemArray);
///         },
///     _dOOdad.MoveNext);
/// 
/// </code></example>
protected virtual IEnumerable<TEntity> FetchRecords(
    Func<bool> loadSubsetFunc, Action preIterationAction,
    Func<TEntity> translateRowFunc, Func<bool> moveNextFunc)
{
    // If records are found, sort them and return set of entities
    if (loadSubsetFunc())
    {
        Trace.WriteLine(string.Format(
            "# FOUND one or more records: Returning {0}(s) as a set.",
            typeof(TEntity).Name));

        int recordCount = 0;

        preIterationAction();

        do
        {
            recordCount++;
            var entity = translateRowFunc();
            yield return entity;
        }
        while (moveNextFunc());

        // This code never gets reached if FirstOrDefault() is used on the set,
        // because the system will assume no more enities need to be returned
        Trace.WriteLine(string.Format(
            "# FINISHED returning records: {0} {1}(s) returned as a set.",
            recordCount, typeof(TEntity).Name));
    }
    else
    {
        Trace.WriteLine(string.Format(
            "# ZERO records found: Returning an empty set of {0}.",
            typeof(TEntity).Name));
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑(添加解决方案;谢谢@Servy和@BenRobinson):

try
{
    do
    {
        recordCount++;
        var entity = translateRowFunc();
        yield return entity;
    }
    while (moveNextFunc());
}
finally
{
    // This code always executes, even when you use FirstOrDefault() on the set.
    Trace.WriteLine(string.Format(
        "# FINISHED returning records: {0} {1}(s) returned as a set.",
        recordCount, typeof(TEntity).Name));
}
Run Code Online (Sandbox Code Playgroud)

Ser*_*rvy 6

是的,您可以finally在迭代器块中提供块,是的,它们旨在处理这种确切的情况。

IEnumerator<T>实现IDisposable. 当编译器将迭代器块转换为实现时,Dispose枚举器的方法将执行应根据枚举器被释放时当前所在位置执行的任何finally块。

这意味着只要迭代的人IEnumerator确保始终处理其枚举器,您就可以确保您的finally块将会运行。

另请注意,using块将转换为try/finally块,因此基本上将按照迭代器块中所希望的方式工作。如果需要,当迭代器被释放时,它们将清理给定的资源。