Amazon.com Widgets

The danger of over simplification: Enum.IsDefined()

I just stumbled across this guideline in the Design Guidelines document and I thought it needed more explanation..

 

Do argument validation. Do not assume enum arguments will be in the defined range. It is legal to cast any integer value into an enum even if the value is not defined in the enum.

public void PickColor(Color color) {

   switch (color) {

          case Red:

                 ...

                 break;

          case Blue:

                 ...

                 break;

          case Green:

                 ...

                 break;

          //repeat for all known values of Color

          default:

                 throw new ArgumentOutOfRangeException();

                 break;

    }

    //do work
}

 

Note: do not use Enum.IsDefined() for these checks as it is based on the runtime type of the enum which can change out of sync with this code. 

 

There are really two problems with Enum.IsDefined().  First it loads reflection and a bunch of cold type metadata making it a deceptively expensive call. 

Secondly, as the note alludes to there is a versioning issue here.  Consider an alternate way to write the method defined above:

public void PickColor(Color color) {

   if (!Enum.IsDefined (typeof(Color), color) {
       throw new InvalidEnumArgumentException("color", (int) color, typeof(Color));

   }

   //Security issue: never pass a negative color value

   NativeMethods.SetImageColor (color, byte[] image);

}

 

Callsite:

Foo.PickColor ((Color) -1); //throws InvalidEnumArgumentException()

 

This looks pretty good, even if you know this (mythical) native API has a buffer overrun if you pass a color value that is negative.   You know this because you know the enum only defined postive values and you are sure that any value passed was one of the ones defined in the enum right?  Well, only ½ right.  You don’t know what values are defined in the enum.  Checking at the moment you write this code is not good enough because IsDefined takes the value of the enum at runtime.  So if later someone added a new value (say Ultraviolate = -1) to the enum IsDefined will start allowing the value -1 one through.  This is true whether the enum is defined in the same assembly as the method or another assembly. 

 

public Color {
   Red = 1,
   Green = 2,
   Blue = 3,
   Ultraviolate = -1, //new value added this version
}

Now, that same callsite no longer throws.

Callsite:

Foo.PickColor ((Color) -1); //causes a buffer overrun in NativeMethods.SetImageColor()

 

The moral of the story is also two fold.  First be very careful when you use Enum.IsDefined in your code.  The Second is when you design an API to simplify a situation, but sure the fix isn’t worse than the current problem.

Published 29 November 03 05:37 by BradA

Comments

# Frank Hileman said on November 29, 2003 11:30 PM:
I wasn't too happy to discover, by using a profiler, that Enum.IsDefined is taking a significant fraction of CPU in our app that uses System.Drawing. There are some checks in there, and there is nothing we can do to get rid of them.
# Ian Griffiths said on December 1, 2003 10:58 AM:
Even worse, Enum.IsDefined ignores the [Flags] attribute. Given the following: [Flags] enum Foo { X = 1, Y = 2 } the value Foo.X | Foo.Y is allowed. But Enum.IsDefined indicates that it's not a valid value!
# sharpie said on December 1, 2003 11:36 AM:
Therefore would it be safe to say one should simply not be using Enum.IsDefined for anything at all? If there is no good reason for it, why does it exist? WIll it be deprecated in a future release?
# vitabath said on December 5, 2003 8:25 PM:
# James said on December 16, 2003 6:58 PM:
Why not make Enum.IsDefined inlined at compiletime like a #define -- that's essentially what you're asking us to do in our verification code. If there's a standard thing that we should be doing, there should be a compiler/tool way to make it automated. I guess there's some value to making people look at it, but I think it's less of a benefit than making IsDefined (or some IsDefinedAtLastCompile equivalent) easier to do (and therefore more likely to be done). IMHO, there would probably be more errors by people's manual maintenance of this validation code than by not having to handedit the values and missing the assumptions broken by the introduction of new values.
# Brad Abrams said on February 17, 2004 11:49 AM:
# Brad Abrams said on February 17, 2004 11:53 AM:
# Mathew Nolton Blog said on February 18, 2004 3:24 PM:
# Mathew Nolton Blog said on February 18, 2004 3:57 PM:
# Ken Brubaker said on February 20, 2004 10:46 AM:
For my own reference, I thought I'd compile a quick list of design guidelines added by Brad Abrams, et al.
# Omer van Kloeten's .NET Zen said on April 19, 2004 1:50 PM:
# Omer van Kloeten's .NET Zen said on April 19, 2004 4:29 PM:
# Darrell Norton's Blog [MVP] said on May 25, 2005 9:35 AM:
Geoff wrote a whole bunch of code for dealing with Enums. Enums are annoying because you can cast any...
# 菊池 Blog said on March 14, 2006 2:35 AM:
re: C# Tips: Enumなプロパティのsetterの冒頭は
# C# Enum.Parse() Bug « The Pursuit of a Life said on December 11, 2007 2:21 AM:

PingBack from http://xidey.wordpress.com/2007/12/10/c-enumparse-bug/

New Comments to this post are disabled

Search

This Blog

Syndication

Page view tracker