Jack White wrote:
The issues run much deeper.
Yes, I'm well aware of some of the hair-splitting debates on this in the
C++ community. I still stand by my position though, that the worst of it
is caused by the ability of the user to inject exceptions into
operations that could never throw in a language like C#, such as
assignments, parameter passing, return values, etc.
Simple example: it's impossible to write an exception-safe mutator
template method that returns a value of a user-defined type in C++, as
you know. std::stack<T>::pop() doesn't return the value popped for
precisely this reason.
First, what exception guarantees do the native
.NET types make
You'll need to read the documentation to get this, but usually time with
..NET Reflector is needed to get full information. Note that out of
memory and stack overflow can occur at almost any time, and will result
in the app domain being torn down. This puts a hard limit on how
resilient an individual app domain can be, so you may want (need) to
lower your ambitions in this area.
Can I
safely assume for instance that anytime I call a member function on any
native .NET object, my object will remain completely unaltered if an
exception is thrown (the "strong guarantee")? Or does it only support the
"basic guarantee" for instance, where the object's invariants are maintained
so I can recover (the object isn't corrupted), but the state of the object
is otherwise unknown (maybe I only have to reset the cursor in some stream
object for instance).
This isn't a scientific statement, but a well-written class ought to
hold the strong guarantee for unitary operations (e.g. List<T>.Add), and
hold the basic guarantee for aggregate operations (e.g.
List<T>.AddRange) (but note below that it doesn't). One hopes that the
..NET classes in question are well written, and mostly they are. There
are some dodgy ones in there too, unfortunately (though most of the
crimes are in API design, not implementation).
Your own example provides a simple demonstration of
this situation. First, it's inefficient.
Efficiency is more important than correctness? If your concern is about
strong exception safety, I'd relegate efficiency to the bottom of your
list, because strong exception safety usually implies transactions,
which are (almost) never free.
You construct a backup copy first
when it would be much faster to simply build your new copy as a temporary
and reassign it to the original in an exception-safe way (normally by
exchanging the references but that requires passing it in by reference -
note that C++ can do it this way as well
C# can do this also, via 'ref', which passes in the reference to the
list by reference rather than by value. However, this is rare because it
forces the caller to control all the existing references to that list
instance on the heap and update them with the new reference value it
receives from calling the method. That's why I didn't write the method
that way.
or otherwise rely on a "swap()"
function which simply and safely exchanges each object's internal "handle"
to the data).
If you want to do this, you can't use the .NET collection classes.
You're free to write your own classes to do this of course.
Even rolling it back requires yet more copying .
Yep. Strong exception safety for aggregate operations basically amounts
to starting a transaction and rolling it back. The 'copy the world, copy
back if error' is one of the shortest (in code space) ways of doing
that, which is all I was after (also, not knowing what was happening in
the body of the try..except).
What if
failure occurs however at any given time during the call to "Clear()"
The List<T>.Clear() operation won't fail, but you'll have to take my
word for that! It clears the internal array, and sets an index, but it
doesn't do anything that would throw an exception (except stack
overflow, which you can't do much about, as indicated earlier).
or
"AddRange()".
AddRange() could throw OutOfMemoryException or StackOverflow when
creating the iterator, but again, you can't control that. If one were
passing an arbitrary collection to AddRange(), and threw an exception in
ICollection<T>.CopyTo(), then one could really mess up the list (so
List<T>.AddRange() provides neither exception guarantee). That won't
happen with List<T>.CopyTo() though, because it boils down to
Array.Copy, which shouldn't have any problems (though it specifically
doesn't give guarantees, i.e. it may corrupt the array if it e.g. can't
cast an instance when copying from one array to another).
What is the state of "stuff" at this point.
It's pretty irrelevant, since the application is terminating if either
of the above two exceptions are thrown.
Well, it's already
lost its existing data in the "try" block itself (a no-no)
I don't understand what you mean by this. "stuff" is restored by the
except block, and the only things that can stop that are fatal errors,
due to generics being used in the example (so e.g. Array.Copy can't fail
due to typecasting etc.).
but more to the
point, what if "AddRange()" throws for example. Is "stuff" even in a valid
(uncorrupted) state.
In the most general sense, for any AddRange() call, no, it may be
corrupted.
I assume the C# language
The C# language doesn't much to say in this specific matter. It's down
to how the .NET library classes themselves are written, which is more
part of the CLI specification than C#.
says yes (providing the "basic
guarantee") but even so, is "stuff" itself now partially-filled with data or
does it remain completely intact
No, data may be shifted up, items may be lost, etc.
(clear because of your previous call but
even without clearing it first, what's the state of data you copied into it
in the "try" block).
Exception safety is a recursive concept; the try block in my example is
empty, but of course for the whole thing to be exception safe, the
operations inside must also be exception safe, possibly requiring extra
exception handlers etc.
Moreover, rolling back changes after an error may not
always be practical, efficient, possible,
It always amuses me when C++ programmers put "efficient" ahead of things
like correctness, or even "possible" ;-)
or exception safe itself.
Of course; that's why basic exception guarantees are more usual for
aggregate operations.
The C++
committee spent years hashing these issues out
And I stand by what I said at the start. Another reason why it's so bad
in C++ is lack of GC and stronger guarantees on e.g. bounds-checked
arrays. Exception safety is usually required to ensure that all memory
is freed up, and to avoid writing past the ends of arrays, etc.
and C# probably adopted many
of their decisions
Largely, no, not in this area, I don't think so.
but I'd like to see some white-paper on the subject in
the context of C# itself (whose issues aren't as complex as C++ but exist
nevertheless).
I think that would be good too, beyond what's covered by the reliability
contract and constrained execution region stuff. The approach in .NET is
much more a "best-effort" setup, yet bounded by the app domain, rather
than giving hard guarantees on the framework classes.
-- Barry
--
http://barrkel.blogspot.com/