共用方式為


Enums and validation

We've been talking about enums a bit, in one of those cases where I'm looking into something and I then get a request about that same thing from some other source. Or multiple sources, as it happened this time.

Yesterday, I was working on my TechEd presentation (DEV320  Visual C# Best Practices: What's Wrong With this Code?), and one of my examples deals with enums. The following is a spoiler, so if you come to my talk, you'll need to sit on your hands and look smug during that part of the talk.

First Question:

Consider the following code:

enum Operation
{
    Fold,
    Spindle,
}

class BigExpensiveMachine
{
    public void Initiate(Operation operation)
    {
        // code here
    }
}

what are the possible values of operation at the “code here” comment?

A: The number of possible values is not defined by what you write in the enum, it's defined by the underlying type of the enum, which defaults to int. Which means somebody could write:

bem.Initiate((Operation) 88383);

So, my first point is that enums are not sets - they can take on any value from the underlying type. If you know this, you might already be writing code like:

class BigExpensiveMachine
{
    public void Initiate(Operation operation)
    {
        if (!Enum.IsDefined(typeof(Operation), operation)
            return;
        // code here
    }
}

Q: Does this code solve the problem?

 

A: It solves some of the problem, but there is still a latent issue that can be fairly bad. It is probably fine when first written, but it has no protection for when this happens:

enum Operation
{
    Fold,
    Spindle,
    Mutilate,
}

When I change the enum and recompile, my Initiate() method now accepts “Mutilate“ as a valid value, even though it wasn't defined when I wrote the routine. Whether this is a problem or not depends on what's in the routine, but it could lead to weird behavior or a security issue. IsDefined() is also fairly slow - I took a look at the code, and there's a lot of it.

So, where does that leave us?

When most people ask to be able to validate an enum, what they really want is the ability to limit the enum values to those that they had in mind when they wrote the routine. Or, to put it another way, to support additional values should require a deliberate act on the part of the programmer rather than happening by default.

To get this behavior requires hand-coding the value check as part of the routine. It could either be a separate check:

if (operation != Operations.Fold && operation != Operation.Spindle)
    return;    // or an exception as seems prudent

or as part of the logic of the routine:

switch (operation)
    case Operation.Fold:
        ...
        break;

    case Operation.Spindle:
        ...
        break;

    default:
        return;
}

Either of those constructs ensure that I'm dealing with the world I understand inside the routine. Which is a good thing.

Comments

  • Anonymous
    May 10, 2004
    "enums are not sets"

    Enums aren't very OO, either. If you're worried about your enum getting used incorectly, force correct usage with OO programming techniques.

    For example, check out
    http://www.refactoring.com/catalog/replaceTypeCodeWithClass.html

    http://www.refactoring.com/catalog/replaceTypeCodeWithStateStrategy.html

    http://www.refactoring.com/catalog/replaceTypeCodeWithSubclasses.html

  • Anonymous
    May 10, 2004
    How can I use hyphens in enums? The following fails:

    public enum CssTypes
    {
    background,
    background-color,
    background-image
    }


    thanks,
    -ron

  • Anonymous
    May 10, 2004
    Eric,

    I'd actually change the default case in the switch to throw an exception (InvalidOperationException?), instead of just failing silently. That would make it much more easier to figure out what's going on...

  • Anonymous
    May 10, 2004
    or an InvalidEnumArgumentException...

  • Anonymous
    May 10, 2004
    The comment has been removed

  • Anonymous
    May 10, 2004
    I wrote an small entry on this a while ago too....
    http://dotnetjunkies.com/WebLog/josephcooney/archive/2004/04/08/11002.aspx

  • Anonymous
    May 10, 2004
    Eric, before giving your TechEd presentation, you might want to remove the extra commas in your enumerations. ;)

  • Anonymous
    May 10, 2004
    How do you use hyphens in enumeration values? You don't. The hyphen is not a valid character for identifiers.

    The problem is that hypens, or anything "hyphen-like", such as non-breaking hyphen u2011, figure dash u2012, en dash u2013, and minus sign u2212, are all categorized as Pd (Punctuation, Dash).

    Identifiers, in turn, can only contain characters from the Unicode categories Lu (Letter, uppercase), Ll (Letter, lowercase), Lt (Letter, Titlecase), Lm (Letter, Modifier), Lo (Letter, other), Mn (Mark, non-spacing), Me (Mark, enclosing), Pc (Punctuation, connecting), Cf (Other, format), Nd (Number, decimal digit), and Nl (Number, letter). Not necessarily in that order (numbers can't be the first character of an identifier).

    The above is basically an i18n-aware version of the "standard" regex-like [_w][_dw]+ pattern for identifiers.

    In an ideal world, using @IDENTIFIER notation would save you, as prefixing an identifier with @ enables literal quoting, so you can have @bool to refer to a variable named 'bool'. However, the standard tokenizing rules also apply, so @one-two breaks down to the tokens {'one', '-', 'two'}, which doesn't work for you. If @IDENTIFIER included paranthesis for wrapping then you'd be fine saying @(one-two), allowing us to get all LISP-ish with our naming, but that wasn't done.

    - Jon

  • Anonymous
    May 10, 2004
    Todd, I see no extra commas in Eric's enumerations.

    The trailing comma on the final entry is allowed and is also how I write my enumerations. Saving you having to add it when you want to add a new value!

    Unless you meant something else, then I'm not sure what you mean :)

    n!

  • Anonymous
    May 11, 2004
    The comment has been removed

  • Anonymous
    May 11, 2004
    How about using strings in an enum I made a post about that after looking at your post. http://blog.dotwind.com/archive/2004/05/12/150.aspx
    do you thing there is a way to do what I am trying there?

    thanks

  • Anonymous
    May 11, 2004
    The comment has been removed

  • Anonymous
    May 12, 2004
    How do you verify combinations like Fold + Spindle?

  • Anonymous
    May 12, 2004
    Thomas - you'd be looking to use bitwise logic to verify combinations of enums, for which you need to declare your attribute with the Flags attribute, see below for details:
    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemflagsattributeclasstopic.asp

  • Anonymous
    May 12, 2004
    The comment has been removed

  • Anonymous
    December 18, 2004
    Helpful For MBA Fans.

  • Anonymous
    January 04, 2008
    PingBack from http://actors.247blogging.info/?p=3784

  • Anonymous
    March 20, 2008
    PingBack from http://dinnermoviesblog.info/eric-gunnersons-c-compendium-enums-and-validation/

  • Anonymous
    June 07, 2009
    PingBack from http://greenteafatburner.info/story.php?id=3568