Andreas Huber said:
Daniel,
I fail to see what the underlying layout of a class or an enum has to
do with invariant enforcement. Just like a class an enum is simply an
abstract concept, nothing more nothing less.
I also don't to see why the source of an object (data base, file,
interlanguage call, etc.) should have an impact on whether the
invariants should be enforced or not.
When I save an enum into an external store and reload it later, I'll
just as much expect that the enum will have a legal value as I expect
an object of *any* other type to have a legal value. E.g. If I convert
a string into a DateTime object I'll expect that the object is in a
legal state after conversion or an exception being thrown otherwise.
And this is exactly what DateTime.Parse() does.
And they are introducing DateTime.TryParse() for just that reason, sometimes
an exception is just not acceptable.
Why should an int to enum conversion behave differently than a string
to DateTime conversion? In both cases the problem is the same: that an
object of the source type has many more legal states than an object of
the destination type.
From what you have written so far, I assume that you would simply
allow the invalid enum value be processed and/or displayed by your
program. While I can imagine situations where it is perfectly
reasonable to do so (ignoring a problem and grinding on rather than
dealing with it), I don't think that this is good practice in most
real-world applications. The reasons are as follows:
- Most people prefer a program that refuses to perform an operation
when it detects a problem over a program that produces incorrect
results. This is/was the case in all projects I was involved in
- If you allow enums to have invalid values your *whole* program must
be able to deal with them, i.e. similar code must be spread
everywhere. I'd rather avoid this when I can make the checks once,
i.e. in the persistence or interfacing layer
It would depend on what the enums meaning was. If the enum specified an
operation, or a list entry, etc, then no I wouldn't. However, if it is just
an option and no option is as valid as all options, then yes I would. When I
load an enum, if the enum must have a value I do the checking. If it is
acceptable for it to be empty(granted, these are usually Flags decorated
enums), then I leave it be.
Well, the world is complex, isn't it ;-)? Seriously, wasn't the single
most important argument for C# and .NET that the code is managed, i.e.
that many common programmer errors can and will be detected by the
runtime?
Yes, followed closely by increased simplicity.
I'll ignore the C++ part for the moment as that would be worth a
thread of its own.
What is so complex about having to put a Flags attribute on an enum
that you don't want to have checked? Plus, either the compiler could
help you in many situations or you'd find out very soon when you test
your code for the first time. How this is implemented is of little
importance.
Its semantically wrong, IMHO, to use Flags on an enum that is not a flagged.
I'd argue for a Unchecked attribute before I'd accept using Flags on a
non-flagged enum.
If you need an empty state, give the enum one. Period. I still fail to
see what the enum value being generated outside the runtime has to do
with invariant enforcement.
While thats preferable, it doesn't mean its always possible or it is always
done(not all enums in the world are under my control, for example)
This is such a common misconception. Why do you check the enum to
avoid the exception? As you can expect the exception to only be thrown
very rarely, the checking to avoid the exception will actually slow
the code down, because try blocks are a lot faster than ifs, as long
as no exception is thrown.
Because if I did not generate the enum value, there is NO reason to assume
the exception will be uncommon. If the enum is sourced by a non .NET
language or whatever, there is no way to assume it is correct. Just as if
you were given a file name, do you jsut open the file and catch the
exception if it comes? or do you call File.Exists() to check while still
handling exceptions as they come.
It depends heavily on the context, IMHO. If you are dealing with ONE file,
then just relying on the exception is ok, if you are dealing with many, you
have no idea how common the issue will be and cannot rely on the exception
being rare. It could happen 500000 times, you cannot tell at runtime.
In most cases, I consider enums to be singlar operations, but then again, in
most cases I consider files to be singlar operations too. This change could
potentially seriously cut back on flexibility.
We don't! See above.
And I claim exactly the opposite, decent code never checks to avoid
exceptions that occur only rarely.
Moreover such errors (an enum having an invalid value) can almost
never be dealt with locally. More often than not the error has to be
passed up the call chain over several stack frames. This is exactly
what exceptions were invented for.
I agree, I just don't think the runtime should be throwing the exception. If
there is need for an exception to be thrown, I'll do it. I wrote the code
and at times the runtime is a bit too invasive as it is, this would just
make it more so. There is a line where features for the common start to make
it too difficult to work in the environment for the uncommon, I think this
feature would be one. It would make .NET a much less interesting choice in
enough situations that it would dilute its value to me.
Perhaps an Enum static method equivilent to the DateTime method ParseExact
would be more appropriate? While it wouldn't be called ParseExact,
obviously, as it doesn't deal with strings, but it seems like it would solve
this problem without an issue, allow casts to operate as is(breaking
existing functionality is also unattractive), and add a static member that
throws an exception if the enum value is incorrect, maybe something like
struct Enum
{
//existing enum members
//Performs the same work as a cast
public static object GetEnum(Type enumType, int value);
//Performs the same work as a cast, however checks the enum
//and insures value is a valid type.
public static object GetEnumExact(Type enumType, int value);
//other overloads for possible underlying types. Unfortunatly
//generics cannot take System.Enum as a type constraint, so you are stuck
with a cast
}
The main advantage of this is it would allow both Flags and non-Flags
decorated enums to be validly converted in either situation. It can be that
in one case a Flags enum needs to be precise, but in others needs to be
loose. A specific rule using Flags potentially causes loss of flexibility.
Adding another attribute to the mix fixes that, but adds more complexity and
locks the enum's use to what the author of the enum wants. In some cases the
author cannot know how exactly the enum will be used outside of it being
Flags or a singular enum, or the enum will be of value in another situation.
This will allow the consumer to determine the specifics and help
reuseability. The consumer would have to be responsible for making sure that
it doesn't call a method that expects valid enums without a try\catch
block(or appropriate handling) if the consumer doesn't validate the enum.
This is a situation where adding attributes to a given instance would be of
value, a ValidatedInstanceAttribute or something.