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 (25)

  1. jaybaz [MS] says:

    "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

  2. ron says:

    How can I use hyphens in enums? The following fails:

    public enum CssTypes

    {

    background,

    background-color,

    background-image

    }

    thanks,

    -ron

  3. 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…

  4. David M. Kean says:

    or an InvalidEnumArgumentException…

  5. One thing I find myself doing a lot with enums is having a default XX.Empty tag first (that acts as kind of a default ‘null’ value). Recently I’ve also been using ‘End’ tags too. The advantage is you can very quickly validate a tag (assuming you aren’t using custom values) with a compare, like

    Tag t = Tag.Open;

    if(t > Tag.Empty && t < Tag.End){ // valid }

    The main reason I went with this is I was having problems when inserting new values into the enum during development, it became a pain to hunt simple validation stuff down. When going to a new version, you can just add enums, and test for ‘Tag.End2’.

    Not sure if this is the best way though – it has yet to blow up in my face at least. I don’t always like the idea of a value that is just used as a marker, it really should be a ‘silent’ value. I guess you could use .GetNames.Count or something code heavy like that. Of course switching to defined values would be painful…

    I also usually throw an exception in the default switch clause (until release anyway!) when switching over an enum – just makes it easier to catch where the need to make a change after inserting a new value into an enum.

    It would be great if the switch statement could be made to (optionally) throw an error when switching an enum and every case isn’t explicitly covered.

  6. Todd Girvin says:

    Eric, before giving your TechEd presentation, you might want to remove the extra commas in your enumerations. 😉

  7. Jonathan Pryor says:

    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

  8. nfactorial says:

    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!

  9. Jay Cook says:

    I would try the following, use System.Enum.GetValues and then search the resulting array for a match. This will eliminate the hardcoded switch and if statements. For example,

    using System;

    namespace EnumExample

    {

    enum Operation

    {

    Fold,

    Spindle,

    Mutilate,

    }

    class Class1

    {

    static void Foo(Operation op)

    {

    int value = (int) op;

    foreach(int i in Enum.GetValues(typeof(Operation)))

    {

    if (i == value)

    {

    Console.WriteLine(op.ToString() + " is a member of the enum");

    return;

    }

    }

    // not a member

    Console.WriteLine(op.ToString() + " is not a member of the enum");

    }

    static void Main()

    {

    Foo(Operation.Fold);

    Foo(Operation.Spindle);

    Foo(Operation.Mutilate);

    Foo((Operation) 42);

    }

    }

    }

  10. Tim says:

    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

  11. nfactorial says:

    Tim,

    In application I’m working on required similar functionality. However I wrote an attribute that could be applied to enum members to provide a ‘display name’ for the entry.

    ie.

    public enum MyEnum

    {

    [ EnumDisplayAttribute( "Some Example" )]

    SomeExample,

    }

    This is a bit more work than it looks like you wanted (have to provide a customized combo-box as well) but works well.

    Alternately, you could fake it by having a class with public, const strings:

    public sealed class Example

    {

    private Example() {}

    public const string Example = "An Example";

    }

    n!

  12. Thomas Eyde says:

    How do you verify combinations like Fold + Spindle?

  13. Hugo Rodger-Brown says:

    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

  14. Tim says:

    thanks n!

    I love the idea of the attribute, I will definetly try that.

    the const strings class is the way I have been doing it. I just found it really easy to bind the enum to the combo box and also to store the values in the database instead of having a different table with the categories I will just put it as an enum and store the int value for it in the table.

    Tim

  15. MBA says:

    Helpful For MBA Fans.

Skip to main content