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.