Reputation: 606
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
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
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
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