是否有可能为yield-return方法设置一个'finally'代码块?

时间:2014-03-11 16:37:13

标签: c# ienumerable do-while yield-return

背景

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

当我读取多个数据行时,它为每个实体正确执行yield return,然后在do...while之后到达跟踪消息,没有任何问题。

问题

但我注意到,当我在集合上使用IEnumerable.FirstOrDefault()时,系统假定不再需要返回yield,并且 没有完成此方法的执行 < /强>!

除了返回了多少记录的跟踪输出之外,我对这种行为没有太大的问题,但它让我想到:“如果我确实需要有一些......我们怎么称呼它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));
    }
}

编辑(添加解决方案;谢谢@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));
}

1 个答案:

答案 0 :(得分:4)

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

IEnumerator<T>实施IDisposable。当编译器将迭代器块转换为实现时,枚举器的Dispose方法将执行任何finally块,这些块应根据枚举器处理时的当前位置执行。

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

另请注意,using块将转换为try/finally块,因此基本上可以在迭代器块中实现。如果需要,它们将在处理迭代器时清理给定的资源。