Exception Safety in C#

J

jehugaleahsa

Hello:

As an avid reader of C++ books, I know a lot of programmers out there
are confronted with the challenge of exception safety.

For those who don't know what it is, it is writing code in such a way
that an exception will not cause a class to be left in an unstable
state. For instance, you wouldn't want an OutOfMemoryException leaving
a container with an invalid size or missing elements.

Now, in C++, the main idea is to write code so that you do as much
work as possible withouth doing anything that can cause and exception,
and back track if something does go wrong. In C++, implementors
usually do this by using an idiom called, copy/modify/swap, to achieve
exception safety.

I am wondering if this is easier to do in C#. Assignment in C# has a
different meaning than assignment in C++ for reference types.
Assignment makes things equal, not equivelent. C# natively supports
Clone(), but not really a copy ctor. Since C# uses references, I can't
really change my reference since multiple references may be pointed to
the object I am modifying (some times).

Imagine there is a class that looks something like this:

class Dog
{
private int age = 0;
// . . .
public void CelebrateBirthday()
{
++age;
// . . . something throws an exception at this point
}
}

If I were to execute this code and an exception was thrown, my dog
would be a year older than he really should be (assuming that I want
to rollback (undo) the change in the face of an exception). One
solution would look like this:

public void CelebrateBirthday()
{
try
{
++age;
// . . . something throws an exception at this point
}
catch
{
--age;
// undo potential changes to other fields (may require storing
the old value)
}
}

Now, I would have to write that around every mutating method/property
in my class. YUCK! In a class with dozens of fields, that would be a
nightmare.

This is my proposed solution, similar to how it is done is C++:

private void copy(Dog dog)
{
age = dog.age;
// transfer additional fields
}

public void CelebrateBirthday()
{
Dog tempDog = new Dog();
tempDog.copy(this);
++tempDog.age;
// . . . something throws an exception at this point
this.copy(tempDog);
}

Notice that there is no longer a need for try/catch. If something goes
wrong, the copy, tempDog, just gets wasted. The final line is to
update 'this' with the changes.

Now, this seems really simple to me. I have trouble believing that
someone didn't come up with something more general or correct (I think
mine is correct???). For instance, there is a lot to be concerned
about with deep/shallow copies.

Community-wide approaches are what I am looking for. Anyone?

Thanks,
Travis Parks
 
P

Peter Duniho

[...]
Notice that there is no longer a need for try/catch. If something goes
wrong, the copy, tempDog, just gets wasted. The final line is to
update 'this' with the changes.

Now, this seems really simple to me. I have trouble believing that
someone didn't come up with something more general or correct (I think
mine is correct???). For instance, there is a lot to be concerned
about with deep/shallow copies.

I'm afraid I don't really understand the question. The technique -- to
create a temporary instance on which one operates and then somehow replace
the original with the temporary version -- you describe is both general
and widespread. It applies in a number of scenarios, and not just in OOP
classes (for example, writing to a temp file, and only deleting the
original and renaming the temp file if the temp file is successfully and
completely created).

That said, C++ does not inherently support this technique any more than C#
does. So I don't really understand the comparison you seem to be making
between C++ and C#.

As far as whether the technique is appropriate generally, I'd say much of
the time it's not. It really depends on what you're doing, but creating a
complete copy of an instance, modifying, and then copying all of that back
to the original has a lot of overhead and itself requires implementation
of the Copy() method or something like it. I have yet to run into a
situation where I felt it was unreasonable to simply do whatever cleanup I
need to do locally should an exception occur.

Note also that in your example, you sort of gloss over the fact that the
exception still needs to be caught somewhere. Yes, you can catch it and
just ignore it. But if you're going to have to write the exception
handler anyway, why not have it do something useful, like clean up after
whatever operation the code contained within was doing?

For those times when the technique is appropriate, then sure...I'd agree
that it's, well...appropriate. Would I write it into every one of my
classes as a standard matter? Nope.

Pete
 
J

jehugaleahsa

[...]
Notice that there is no longer a need for try/catch. If something goes
wrong, the copy, tempDog, just gets wasted. The final line is to
update 'this' with the changes.
Now, this seems really simple to me. I have trouble believing that
someone didn't come up with something more general or correct (I think
mine is correct???). For instance, there is a lot to be concerned
about with deep/shallow copies.

I'm afraid I don't really understand the question. The technique -- to
create a temporary instance on which one operates and then somehow replace
the original with the temporary version -- you describe is both general
and widespread. It applies in a number of scenarios, and not just in OOP
classes (for example, writing to a temp file, and only deleting the
original and renaming the temp file if the temp file is successfully and
completely created).

That said, C++ does not inherently support this technique any more than C#
does. So I don't really understand the comparison you seem to be making
between C++ and C#.

As far as whether the technique is appropriate generally, I'd say much of
the time it's not. It really depends on what you're doing, but creating a
complete copy of an instance, modifying, and then copying all of that back
to the original has a lot of overhead and itself requires implementation
of the Copy() method or something like it. I have yet to run into a
situation where I felt it was unreasonable to simply do whatever cleanup I
need to do locally should an exception occur.

Note also that in your example, you sort of gloss over the fact that the
exception still needs to be caught somewhere. Yes, you can catch it and
just ignore it. But if you're going to have to write the exception
handler anyway, why not have it do something useful, like clean up after
whatever operation the code contained within was doing?

For those times when the technique is appropriate, then sure...I'd agree
that it's, well...appropriate. Would I write it into every one of my
classes as a standard matter? Nope.

Pete

My original post asks for a community-wide approach to exception
safety. I wasn't really concerned with the differences between
languages or how the copy/modify/replace idiom applies to various
other aspects of computer science. I simply want to know what other
developers have done to face the problem.

I don't agree that catch blocks should be utilized to undo what the
try was trying to. It is probably one of the most efficient means.
However, programmers are generally forgetful. Having to control that
depth of code inside potentially many catch blocks is a maintenance
nightmare. You would probably create a single method that resets the
values. But then you need to know what to reset them to. So you need
to store those values somewhere.

I am just suggesting (as it makes sense to group related data) to
create a copy. You don't have to muddy up your code with try/catch
then. And I am not all too concerned about handling the exceptions. As
such, I don't even want to make my code *appear* to be concerned about
them; it should just do the right thing.

I don't think the implementation of a copy() method would be a big
chore. You saw that it can be fairly easy. You are just saving your
state. assignment assignment assignment . . .

Now here is the dilemma . . . since we created a copy of our class, we
cannot call any methods that create a copy of our class without
redundant copies being made. So we have to assume that we are only
dealing with the fields directly or methods that are not exception
safe. It seems like a huge limitation. However, I think this presents
a wonderful opportunity! I think this suggests that we create
interface methods and implementation methods, where one simply
delegates to the other. We can then formalize the copy/modify/replace
process.

public class Dog
{
public void CelebrateBirthday()
{
Dog tempDog = new tempDog(); // all public modifying methods
begin and end the same way
tempDog.celebrateBirthday(); // implementation method
this.copy(tempDog);
}
}

Now, personally, I do not think copying is a large waste. We are
talking about just a few bytes for typical classes (most of which
would need to be stored to undo changes anyway). We could probably
even get away with reduntant copies if we wanted to. It depends how
anal we are about efficiency. If we truly wanted, we could even make
the state of the class a private struct with public fields and leave
ourselves open to enter the realm of the state pattern (if needed) and
just copy/modify/replace it.

We need to store the original state somehow, somewhere. We originally
created the class to enclose that data. Why not use it?

Now, from my experience, I rarely deal with exceptions locally - I
don't know how to; it's not always plausible/possible. At most I will
use a finally block to "clean up". However, clean up always takes
place, so I can't put an "undo" operation here. I feel stupid dealing
with clean up in each catch or even one catch. Your time-traveling Dog
ends up looking like this:

public void CelebrateBirthday()
{
int ageChange = new Random().Next(10); // what does -1 mean for a
Dog that can travel to the past?
try
{
age += ageChange;
}
catch
{
age -= ageChange;
}
}

My version would be about three lines long. I don't need to worry
about the scope of ageChange; I don't need to handle all the other
variables that go into my time-travelling Dog. True, I only need to
store the variables I change, but how do I know one of the methods
inside my method won't change someday to alter additional ones? Other
programmers aren't going to understand why you have all the additional
lines of code, just to add a few years to a poor dog's life. Where we
create an implementation method, the code is straight forward because
it removes all exception handling. It just does what it says it does
and nothing else. Since we can formalize the copy/modify/replace in
every public method, my programmers only need to remember one thing
and not need to understand (they can practically forget, like most
programmers do anyway) about exception safety. If efficiency is
absolutely critical, your method is pure adrenaline. But in general,
this approach is more readable and more maintainable, and hardly less
efficient.

Let me know if I am getting on your nerves. I think I have something
that is not just occassionally appropriate; I think it is appropriate
(not always the most efficient) for just about any exception-safe
class. I think efficiency gains should be made after a profiler has
said you should. It all comes down to a memcopy my friend; it just a
matter of how big the loop is.

~Travis
 
B

Ben Voigt [C++ MVP]

Peter Duniho said:
[...]
Notice that there is no longer a need for try/catch. If something goes
wrong, the copy, tempDog, just gets wasted. The final line is to
update 'this' with the changes.

Now, this seems really simple to me. I have trouble believing that
someone didn't come up with something more general or correct (I think
mine is correct???). For instance, there is a lot to be concerned
about with deep/shallow copies.

I'm afraid I don't really understand the question. The technique -- to
create a temporary instance on which one operates and then somehow replace
the original with the temporary version -- you describe is both general
and widespread. It applies in a number of scenarios, and not just in OOP
classes (for example, writing to a temp file, and only deleting the
original and renaming the temp file if the temp file is successfully and
completely created).

That said, C++ does not inherently support this technique any more than C#
does. So I don't really understand the comparison you seem to be making

Sure it does, with compiler-generated default operator=.
between C++ and C#.

As far as whether the technique is appropriate generally, I'd say much of
the time it's not. It really depends on what you're doing, but creating a
complete copy of an instance, modifying, and then copying all of that back
to the original has a lot of overhead and itself requires implementation
of the Copy() method or something like it. I have yet to run into a
situation where I felt it was unreasonable to simply do whatever cleanup I
need to do locally should an exception occur.

Note also that in your example, you sort of gloss over the fact that the
exception still needs to be caught somewhere. Yes, you can catch it and

The only place it needs to be caught, is at a layer of code that can *fix*
it (Tell the user to save somewhere else, retry the upload, etc). Any
low-level code is just going to have to rethrow it, better to not catch it
(and mess up the stack trace) in the first place. And yes, even a rethrow
messes up the stack trace.
just ignore it. But if you're going to have to write the exception
handler anyway, why not have it do something useful, like clean up after
whatever operation the code contained within was doing?

For those times when the technique is appropriate, then sure...I'd agree
that it's, well...appropriate. Would I write it into every one of my
classes as a standard matter? Nope.

Hmm, C++ programmers do provide support nearly always. They either accept
the compiler-generated operator=, write their own, or explicitly disable
(and this is rare) assignment.
 
P

Peter Duniho

[...]
That said, C++ does not inherently support this technique any more than
C# does. So I don't really understand the comparison you seem to be
making

Sure it does, with compiler-generated default operator=.

That's not my point. You still need to write the code that _uses_ that,
and because the default operator= does a shallow copy, it only works for
the simplest of classes. You can easily accomplish the same thing in C#
with a few extra lines of code in the class, but neither language provides
a universal solution.
[...]
The only place it needs to be caught, is at a layer of code that can
*fix* it (Tell the user to save somewhere else, retry the upload, etc)..
Any low-level code is just going to have to rethrow it, better to not
catch it (and mess up the stack trace) in the first place. And yes,
even a rethrow messes up the stack trace.

I haven't ever found the behavior related to rethrowing exceptions to
interfere with debugging a problem. Maybe that's because when I rethrow
exceptions, I generally reference the current exception as the
InnerException of the new exception. In any case, I would prefer to
sacrifice some efficiency in the exceptional case, rather than incur a
loss in efficiency in all cases.

A matter of preference, I realize. But don't discount it out of hand just
because you don't happen to agree.
[...]
Hmm, C++ programmers do provide support nearly always. They either
accept the compiler-generated operator=, write their own, or explicitly
disable (and this is rare) assignment.

I have no idea what you mean by this? Are you claiming to know what the
general population of all C++ programmers do as a matter of habit?

I've seen a LOT of C++ code in my day, and very little of it uses the
operator= with a C++ class at all, never mind accepts the
compiler-generated operation, implements their own, or explicitly disables
it.

Your experience may be different, but it seems to me you should be careful
about drawing broad generalizations based solely on your own personal
experience.

Pete
 
P

Peter Duniho

[...]
Let me know if I am getting on your nerves. I think I have something
that is not just occassionally appropriate; I think it is appropriate
(not always the most efficient) for just about any exception-safe
class.

You're not getting on my nerves. I'm just offering my own viewpoint. I
thought that's what you were asking for.

Just because I happen to disagree with you, that shouldn't make you think
you're getting on my nerves. It would be pretty stupid to be annoyed at
someone just because they disagreed with you. Wouldn't it?

Pete
 
M

Mark Wilden

I think an assumption is being made that one can come up with a standard way
of handling unexpected conditions. I don't believe this is possible. The
copy-update-swap method could still generate an exception during the swap,
after all.

If an exception is considered "possible" (like not having enough disk space
to write a file), then it should be wrapped in a try-catch and retried
appropriately. But most exceptions are not considered "possible." Many of
them result from bugs, for example. So who knows what the app should do if
it does catch an exception it didn't think was possible? Not much, IMO.

I think the only realistic way to handle exceptions in a general way is
simply to throw away the work that's been done and start over (which may
involve restarting the application). If database changes are occurring, then
the "throwing away" part requires transactions at the code level, which
aren't committed when there's an exception.

These are just some general thoughts about a general question. As always, it
helps to provide a concrete, realistic example when proposing a pattern. In
the case of the dog's birthday, even if the dog was rolled back, what should
the rest of the app do to recover from the exception? If it's just to print
a message and exit, who cares about the dog?

///ark
 
M

Mark Wilden

try
{
age += ageChange;
}
catch
{
age -= ageChange;
}
}

Presumably, there would be additional code in the try-block, possibly both
before and after the assignment. How would the catch-block know what to roll
back, without introducing flags, or a try-catch around each statement, or
something equally unwieldy?

If the app experiences a totally unexpected exception, I think restoration
of the dog's state is the least of its worries.

///ark
 
J

jehugaleahsa

[...]
Let me know if I am getting on your nerves. I think I have something
that is not just occassionally appropriate; I think it is appropriate
(not always the most efficient) for just about any exception-safe
class.

You're not getting on my nerves. I'm just offering my own viewpoint. I
thought that's what you were asking for.

Just because I happen to disagree with you, that shouldn't make you think
you're getting on my nerves. It would be pretty stupid to be annoyed at
someone just because they disagreed with you. Wouldn't it?

Pete

Well, I didn't want you to think I was trying to contradict you or
anything.

~Travis
 
J

jehugaleahsa

I think an assumption is being made that one can come up with a standard way
of handling unexpected conditions. I don't believe this is possible. The
copy-update-swap method could still generate an exception during the swap,
after all.

I don't think there is a "standard" way of handling exceptions; this
post proves it. This is just something my group can agree to do for
our classes, if we need to or we don't want to deal with exceptions in
some other manner. There is something very important about swap; it
must be exception safe. I think that if we are careful, we can make
all swaps nothing but assignments to existing objects, which would be
exception safe. As long as you don't try to construct anything on the
heap, you should be fine. Since your copy is created prior to any code
executing, you know it is stable before we begin doing any work.
If an exception is considered "possible" (like not having enough disk space
to write a file), then it should be wrapped in a try-catch and retried
appropriately. But most exceptions are not considered "possible." Many of
them result from bugs, for example. So who knows what the app should do if
it does catch an exception it didn't think was possible? Not much, IMO.

I would like to get away from exception handling. I think it is its
own subject. You are a bit of a fatalist when it comes to exception
handling. I agree that many bugs are hard to deal with or should not
be dealt with at all. Personally, I put up walls in my code to handle
exceptions at certain levels when I want to protect higher layers.
However, every application requires something somewhat different.

I think the only realistic way to handle exceptions in a general way is
simply to throw away the work that's been done and start over (which may
involve restarting the application). If database changes are occurring, then
the "throwing away" part requires transactions at the code level, which
aren't committed when there's an exception.

I am trying to "start over". My Dog could have been around for a long
time before a method gets called. My Dog is pretty popular, getting
put in lots of collections. He has a lot of references out there. I
may even have different layers of code touching this pup. It might not
be possible to just back up and start over, if, say, my Dog is the top
most class in my application. I am localizing the changes to a small
section of code. I think it is, well, cute the way I do it.

Say that we don't know exactly how much a class is going to be used.
One day we may agree that the Dog should be put down if he fails. The
next day we decide that every Dog should have their day, so we give
them more responsibility. If we didn't want out poor Dog getting wiped
out during an error, we will need to change how he faces exceptions.
Why not write my code the first time through, and never need to worry
about where code will go.
 
J

jehugaleahsa

Presumably, there would be additional code in the try-block, possibly both
before and after the assignment. How would the catch-block know what to roll
back, without introducing flags, or a try-catch around each statement, or
something equally unwieldy?

If the app experiences a totally unexpected exception, I think restoration
of the dog's state is the least of its worries.

///ark

Again, you can't foretell the Dogs influence on the application until
you write it.

When you talk about introducing flags, it goes back to my examples
from before. The idea is that you undo *all* changes if something goes
wrong anywhere in the method. More than likely, Dog can't deal with
the exception at all, so just reset the Dog and let the exception
propagate. We are not trying to resolve exeptions and keep going, we
are just trying to prevent unstable classes. The point of the copy
method is to give a point to go back to and to put it somewhere
consistent and easy to grab.
 
B

Ben Voigt [C++ MVP]

I dunno what's up with OE, not properly prefixing quoted material... so
top-posting this time.

First, compiler generated operator= does not do a shallow copy. It calls
operator= for each member, which is either shallow or deep depending on the
implementation of the member.

If you have C++ code with no operator=, that means the compiler-generated
default is being used. Every class has operator= unless the programmer goes
to extra work to prohibit it.


[...]
That said, C++ does not inherently support this technique any more than
C# does. So I don't really understand the comparison you seem to be
making

Sure it does, with compiler-generated default operator=.

That's not my point. You still need to write the code that _uses_ that,
and because the default operator= does a shallow copy, it only works for
the simplest of classes. You can easily accomplish the same thing in C#
with a few extra lines of code in the class, but neither language provides
a universal solution.
[...]
The only place it needs to be caught, is at a layer of code that can
*fix* it (Tell the user to save somewhere else, retry the upload, etc).
Any low-level code is just going to have to rethrow it, better to not
catch it (and mess up the stack trace) in the first place. And yes, even
a rethrow messes up the stack trace.

I haven't ever found the behavior related to rethrowing exceptions to
interfere with debugging a problem. Maybe that's because when I rethrow
exceptions, I generally reference the current exception as the
InnerException of the new exception. In any case, I would prefer to
sacrifice some efficiency in the exceptional case, rather than incur a
loss in efficiency in all cases.

A matter of preference, I realize. But don't discount it out of hand just
because you don't happen to agree.
[...]
Hmm, C++ programmers do provide support nearly always. They either
accept the compiler-generated operator=, write their own, or explicitly
disable (and this is rare) assignment.

I have no idea what you mean by this? Are you claiming to know what the
general population of all C++ programmers do as a matter of habit?

I've seen a LOT of C++ code in my day, and very little of it uses the
operator= with a C++ class at all, never mind accepts the
compiler-generated operation, implements their own, or explicitly disables
it.

Your experience may be different, but it seems to me you should be careful
about drawing broad generalizations based solely on your own personal
experience.

Pete
 
P

Peter Duniho

[...]
First, compiler generated operator= does not do a shallow copy. It calls
operator= for each member, which is either shallow or deep dependingon
the
implementation of the member.

Sorry for the confusion. Yes, I am mixing up "shallow/deep" with
"copy/clone" in my terminology. What I should have said is that
C++ doesn't _clone_ the object, and that is what I meant.

Basically, the problem I'm trying to describe is that for the "copy" to be
helpful in this case, you need to allocate a completely new data
structure, through its entire depth. Without doing that, there is no way
to be sure that whatever exception occurred has left one of the copies in
a reliable state, because they would share data. Trash one, and you trash
the other.

And again, C++ does not implicitly support this any more than C# does.
The OP wrote "Assignment in C# has a different meaning than assignment in
C++ for reference types. Assignment makes things equal, not equivelent
[sic]". I'm not sure what he's trying to say there. Assigning a
reference in C# is like assigning a pointer in C++, and as you can
dereference a pointer in C++ to copy the members (by assignment) instead
of the pointer, you can use the memberwise-clone method to do the same in
C#.

But in either language, you have to do some real work to get a clone
that's useful for the purposes being described here.

Pete
 
M

Mark Wilden

I am trying to "start over". My Dog could have been around for a long
time before a method gets called. My Dog is pretty popular, getting
put in lots of collections. He has a lot of references out there. I
may even have different layers of code touching this pup.

By "start over," I mean exit the application, restart it, and reload the dog
from persistent store.
It might not
be possible to just back up and start over, if, say, my Dog is the top
most class in my application.

Considering all the nasty thing that can happen to a computer program while
it's running, it had -better- be possible to just back up and start over.
Say that we don't know exactly how much a class is going to be used.
One day we may agree that the Dog should be put down if he fails. The
next day we decide that every Dog should have their day, so we give
them more responsibility. If we didn't want out poor Dog getting wiped
out during an error, we will need to change how he faces exceptions.

But how do you know your decision is correct? If an exception is thrown,
that you are not expecting, and have no idea how to handle, should the dog
continue to exist? Answer: You have no idea. Hence, you must assume the
worst, and exit the application. Better that than take the chance of making
matters worse. And if that's the case, you don't need to worry about
maintaining every object's state.

Again, the best way to think about these sorts of things is to come up with
a concrete example. I can't think of an example where, if you got a
completely unexpected exception, that you'd want to carry on with all the
objects in the system maintaining their prior states. At the very least, I'd
expect such examples to be rare. However, I'm here to learn.

///ark
 
M

Mark Wilden

We are not trying to resolve exeptions and keep going, we
are just trying to prevent unstable classes.

Preventing unstable classes is only a meaningful goal if you try to resolve
exceptions and keep going. It's the latter that I question.

///ark
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top