Salvatore Sorbello
Salvatore Sorbello

Reputation: 606

Perform some operation when a foreach will break

I'm trying to implement the IEnumerable interface for one of my class:

public class NdbScanTableTuple<TKey,TRow> : NdbTableTuple<TKey,TRow>,IEnumerable<TRow>
{
   public IEnumerator<TRow> GetEnumerator()
    {
        yieldTransaction = StartTransaction();
        NdbScanOperation scanPub = yieldTransaction.ScanTable(m_scanRow.NdbRecordValue,           NdbOperation.LockMode.LM_Read);

        //some error checking in the middle... and than the loop
        TRow tmpRes;
        while (scanPub.nextResult<TRow>(ref m_scanRow.ManagedRow) == 0)
        {
            tmpRes = m_scanRow.GetValue();
            yield return tmpRes;
        }           
        yieldTransaction.Close();

    }

    public void CloseYieldedTransaction() 
    {
        yieldTransaction.Close();
    }

}

i use the code in this way: NdbScanTableTuple m_protoTable;

        //init and some other things...

        foreach (protoprocRow proto in m_protoTable) 
        {
            if (proto.col_name == protoprocName) 
            {
                m_protoStruct = proto;
                m_protoTable.CloseYieldedTransaction();
                break;
            }
        }

This because i MUST close the transaction when i exit from the loop. Now my question is: there is a way to execute the yieldTransaction.Close() without calling the CloseYieldedTransaction() explicitly when i prematurely terminate the foreach loop?

Upvotes: 0

Views: 110

Answers (3)

Enigmativity
Enigmativity

Reputation: 117134

You need to implement IEnumerator<T> like this:

public class NdbScanTableTuple<TKey,TRow> : IEnumerable<TRow>
{
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    public IEnumerator<TRow> GetEnumerator()
    {
        return new Enumerator<TRow>();
    }

    private class Enumerator<T> : IEnumerator<T>
    {
        object IEnumerator.Current { get { return this.Current; } }
        public T Current { get { return tmpRes; } }

        public void Dispose()
        {
            yieldTransaction.Close();
        }

        public bool MoveNext()
        {
        }

        public void Reset()
        {
        }
    }
}

(I've left a bit of the detail for you.)

That's why it's not working.

You'll notice that Dispose will do the clean up for you naturally.

Upvotes: 1

Luaan
Luaan

Reputation: 63772

You have no idea the enumeration was stopped. The method simply isn't called anymore, but you can't know that.

A way to ensure the transaction is closed would be to use IDisposable in a using statement, and the proper finalizer in the place where the native resources (most likely sockets - you don't have to handle that, it's already handled) are. The tricky part is you really can't rely on any managed resources still being there in a finalizer, so you have a problem.

When you are using IDisposable, foreach will call Dispose when it's going out of scope, so that's good. Of course, when you're not using a foreach, many of the automated things fall apart.

Now, yield return makes it's own IEnumerator. However, it does handle disposal properly. The only thing you have to do is something you should always do: use try - finally:

public IEnumerator<TRow> GetEnumerator()
{
  var yieldTransaction = StartTransaction();

  try
  {
    NdbScanOperation scanPub = yieldTransaction.ScanTable
       (m_scanRow.NdbRecordValue, NdbOperation.LockMode.LM_Read);

    //some error checking in the middle... and than the loop
    TRow tmpRes;
    while (scanPub.nextResult<TRow>(ref m_scanRow.ManagedRow) == 0)
    {
      tmpRes = m_scanRow.GetValue();
      yield return tmpRes;
    }           
  }
  finally
  {
    yieldTransaction.Close();
  }
}

Learn to use finally - every managed finalization you're doing should be in finally, it's quite critical for proper in-depth exception handling.

Upvotes: 1

Damien_The_Unbeliever
Damien_The_Unbeliever

Reputation: 239764

Rather than use the iterator style of function, you should actually create a (possibly nested) class that implements the IEnumerator<T> interface and contains the required resources to perform iteration (such as your transaction object).

That class can then contain the transaction object and Close it from within the iterator's Dispose method which is automatically called when the foreach terminates (whether normally or abnormally).

This will also make your design cleaner since at the moment, it appears that your storing the transaction as a member of the class, which makes your current class unsafe for multiple threads to enumerate (or even for a single thread to obtain multiple enumerators).

Upvotes: 2

Related Questions