Question

Background

Hi, everyone. I have an abstract class called BaseRecordFetcher<TEntity> it has one method that takes read/sort/translate/move-next methods from the child class, and yield returns the results as model entities.

When I read multiple data rows, it correctly does a yield return for each entity, then reaches the Trace message after the do...while without any problems.

Problem

I have however noticed that when I use IEnumerable.FirstOrDefault() on the collection, the system assumes no more yield returns are required, and it does not finish execution of this method!

Other than the trace-output on how many records were returned, I do not have too much of a problem with this behaviour, but it got me thinking: "What if I do need to have some... let's call it finally code?"

Question

Is there a way to always ensure the system runs some post-processing code after yield return?

Code

/// <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));
    }
}

EDIT (added solution; thank you @Servy and @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));
}
Was it helpful?

Solution

Yes, you can provide finally blocks within an iterator block, and yes, they are designed to handle this exact case.

IEnumerator<T> implements IDisposable. When the compiler transforms the iterator block into an implementation the Dispose method of the enumerator will execute any finally blocks that should be executed based on where the enumerator currently was when it was disposed of.

This means that as long as whoever is iterating the IEnumerator makes sure to always dispose of their enumerators, you can be sure that your finally blocks will run.

Also note that using blocks will be transformed into a try/finally block, and so will work essentially as one would hope within iterator blocks. They will clean up the given resource, if needed, when the iterator is disposed.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top