Hi
Would it not be a good idea to have the using statement recognize the following interface in addition to IDisposable:
interface ISentinel
{
void NormalExit();
void ExceptionalExit(Exception x);
}
Then one could write a Transaction object as follows:
class Transaction: IDisposable, ISentinel
{
public void Dispose() { /* release resources */ }
public void NormalExit() { Commit(); }
public void ExceptionalExit(Exception x) { Rollback(); }
}
using (Transaction t = new Transaction()) { DoSomeStuffWith(t); }
And the commit/rollback semantics are handled automatically. This is good design, because it applies the principle of locality (related code is close together). The equivalent try/catch construction would be something like:
Transaction t = new Transaction();
try {
DoSomeStuffWith(t);
t.NormalExit();
t.Dispose();
} catch (Exception x) {
t.ExceptionalExit(x);
t.Dispose();
throw;
}

"using" enhancement proposal
poodle
It is a nice idea. However, the exception handling logic usually depends on both the context and the type of exception. The class often does not have adequate info to handle possible exceptions, but the caller does. As a result, IMO, ExceptionExit is not of much use.
Regarding NormalExit, caller should explicitly call it to clarify what it want to do; and that does not mess up the code too much. Therefore, I don't think it is much useful either :)
Thi
http://thith.blogspot.com
Nagaraj K
void ExceptionalExit(Exception x)
{
if (x is SqlException) { /* do something */ }
else { / * do something else */ }
}
Why should NormalExit be explicitly called What for The whole point is to take control of that away from the calling code.
Ehsan_AIUB
I meant we often need context information to handle exceptions, and also to decide what to do next on normal exit - different logic for different scenarios. Dispose method does not suffer from that since in most scenarios we want to dispose the same thing of the object. I just think it does not have enough usage scenarios to support and make it a language feature.
Thi
http://thith.blogspot.com
Sam Tyson 92
"Changing an existing pattern at this stage would just be confusing" That's a cop-out. This mechanism would operate completely independently of the existing patterns. And I can think of many other things it could be used for.
For example: a custom trace object that allows you to provide a history of actions performed in the event of an exception. The trace would be automatically added to the exception message or data dictionary of whatever exception is thrown. I know what you're going to suggest - "just add it to the exception before you throw it". But what if I didn't throw it What if it was a divide-by-zero or a null reference Okay, so I can catch it in a catch block. But this means I'd have to write some (potentially quite intricate) code in the catch block every time I wanted to use this pattern.
Here's another example. You must have used something like "try { operation.Process(); } catch (Exception x) { MessageBox.Show("some standard user message .......); DoSomeOtherStuff(); }". Wouldn't that be much better expressed as "using (new UserVisibleExceptionGuard()) operation.Process();" One could hook unhandled application exceptions to achieve the same sort of thing, but this doesn't work when one has nested exception handlers.
The point of all this is to be able to wrap up recurring try/catch patterns into objects. The name of the game is encapsulation. I think there's a lot of value to be derived here from a very small enhancement, which wouldn't have any impact on existing code.
Caleb T
The semantics of existing Transaction classes in the .NET Framework is that Dispose will rollback the transaction unless it has already been committed.
I.e. the pattern looks like:
using(Transaction t = new Transaction())
{
// Do work within the transaction scope
// If an exception is thrown here, the transaction will be rolled back by t.Dispose()
t.Commit();
}
The only change your pattern brings is to remove the need for explicitly calling Commit(). One could argue about which pattern is better, but changing an existing pattern at this stage would just be confusing.
Alexey Rokhin
This is an interesting idea, however, I don't think it's really usable in the real world.
Consider your example. If DoSomeStuffWith(t) fails and you roll back, aren't you going to want to do something else Record it in the event log Alert the user How would the tranaction object know what message to use in either of those cases In practice, you'd end up having to create a subclass of Transaction with a different ExceptionExit for each place you using that construct. Or, you have to make an all-purpose Transaction class, which handles multiple onFailure options:
using (Transaction t = new Transaction(TransactionErrorOutput.ToEventLog, "DoSomeStuff failed on {0}" ))
{ DoSomeStuffWith(t); }
This would not be saving you any lines of code, it wouldn't be making the code any cleaner.