Have you been burned by versioning enums


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Kit George is working on a guideline
around versioning wrt Enums and he needs your feedback. style="mso-spacerun: yes">  "urn:schemas-microsoft-com:office:office" />


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> 


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">It’s a know issue that adding values
to enums is bad (from a breaking change perspective), WHEN someone is
exhaustively switching over that enum. For example:


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">I have an enum, with three elements
in it, and I have some API, which I have written to return a
Color:


style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'">public enum
Color { style="COLOR: blue">Red , Green, style="COLOR: blue">Blue };


style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'">public
class Service
{


style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'">public static
Color
GetColor() {


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">     
// returns a valid Color


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">}


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">}


 


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Someone consumes this enum by
writing this (broken) code:


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">           
Color c = style="COLOR: blue">Service.GetColor();


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">     
switch (c) {


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">           
case style="COLOR: blue">Color.Green :


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">           
    Console.WriteLine(“taking
some action based on Green”);


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">           
    break;


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">           
case style="COLOR: blue">Color.Blue :


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">           
    Console.WriteLine(“taking
some action based on Blue”);


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">           
    break;


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">           
default : // Just
assume Red is the only other value


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">           
    Console.WriteLine(“taking
some action based on Red”);


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">           
    break;


style="FONT-SIZE: 10pt; FONT-FAMILY: 'Courier New'">     
}


 


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">The issue is: what happens when I
want to add more colors to my enum? For example, I want to change Color, so that
now, it has this definition:


style="FONT-SIZE: 10pt; COLOR: blue; FONT-FAMILY: 'Courier New'">public
enum Color {
Red , Green,
Blue, Yellow,
Purple };


 


style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Questions



  • style="MARGIN: 0in 0in 0pt; mso-list: l0 level1 lfo1; tab-stops: list .5in"> style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Have you, or your team ever hit
    this problem with a managed API being updated in this way (an element added to
    an enum), and affecting your code?

  • style="MARGIN: 0in 0in 0pt; mso-list: l0 level1 lfo1; tab-stops: list .5in"> style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Same question, but for an
    unmanaged API?

  • style="MARGIN: 0in 0in 0pt; mso-list: l0 level1 lfo1; tab-stops: list .5in"> style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">If you weren’t aware of this issue
    previously, do you think it might affect your code in the future?

  • style="MARGIN: 0in 0in 0pt; mso-list: l0 level1 lfo1; tab-stops: list .5in"> style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">What if we simply allow this kind
    of change: do you think it’s that bad?

style="FONT-SIZE: 10pt; FONT-FAMILY: Arial"> 

Comments (19)

  1. Michael Teper says:

    I think this type of change is perfectly acceptable, and in fact I’ve frequently started out with enums that I know I will expand at a later time. As a sidenote, in cases where the value of the enum is persisted into a database, I explicitly assign numeric values.

  2. I like to handle every (known) value of the enumeration explicitly, then end the switch with something like:

    default: assert(false); // TODO: handle new enum value!

    return ("some mostly harmless default value");

    This way, the new enum values make themselves known during testing, and the application can still limp along when compiled for release.

  3. Olivier Le Pichon says:

    A better way to improve enum evolutions should be to modify C# switch/case rules.
    This rule could be:
    “A switch on an enumeration type must provide a case for each possible value (either by using case keyword for each value or by using default).”
    Following should be a compilation error:
    enum Color
    {
    Red,
    Green,
    Blue,
    Black
    }
    public static void Main()
    {
    Color c = Color.Red;
    switch(c)
    {
    case Color.Red:
    case Color.Green:
    case Color.Blue:
    break;
    } // *** Error : Color.Black case is missing
    }

    Obviously, we should never use default to be able to benefit from this.

  4. Tene says:

    Imho, that’s not a problem at all, that’s the way default is intented to work, it’s not a "I don’t want to have to write all the *current* possible value" stuff, but rather something like: "I want to be able to do something if my value is not really what I expected".

    In your example assuming "red" is the default is a stupid as assuming that no more windows version will come out so I can hard code version test… or assuming that noone will use this program after 2000… isn’t there a guideline about that? 😉

    Imho, being able to use default is the key to support foreward compatibility as long as it’s used correctly. Maybe if we need a "clean language" that force us to be "correct", we could force developer to handle the "default" case when switching, but it wil be a pain in lot of case.

    Also, in lot of real case, the behaviour of the new application will be "display red" instead of the needed color, but what was working before still work…

  5. Jeff Lewis says:

    I have bumped into this before as well…

    While I wouldn’t mind if enum values are looked up at runtime, I don’t think that it is worth the cost of doing so. If enums where to become inefficient, some might revert to Java-like, const static int’s…

  6. Kevin Daly says:

    It’s not a problem with enum (at least, not only enum) so much as a problem with how you use switch. The assumption lies in coding a default case where you assume you know what the value is: well, if you know, why don’t you test for it explicitly?
    Adding a "case Color.Red" to the switch and using default to test for truly unknown values (which in the case of an enum would normally mean you were testing for something that’s been added) would take care of the problem.
    "default" should not mean "known values I’m too lazy to test for", unless of course there are a *lot* of them and any new values would be handled by the same logic.

  7. Michael Giagnocavo says:

    In switches for enums, I let the default throw an NotImplementedException if it should — when a new enum value implies that more code must be written to handle it. If someone writes a switch like that, and they have problems later, it’s their fault for writing poor code.

  8. I agree with the conclusion that this is NOT a problem of the enum, but an issue of the manner in which the code consuming the enum is written. So insofar as this example illustrates and expresses your issue, no special versioning is necessary.

    However, there IS another, more complex and (probably) rarer issue which you are probably aware of where when two separate assemblies communicate, passing an enum, each of them using their own personal copy of a third enum-containing assembly (that does not have a strong name). If one of the assemblies has a different version with different values for its members (due, say, to inserted members) they can exchange incorrect enum values. I don’t know of any way this could be solved in the compiler, without strong naming (which I think is the right solution).

  9. I like to assign integer values to my enumerations, borrowing from the old BASIC line numbering notion.

    public enum Color{ Red = 100, Blue = 200, Black = 300}

    then, if I add a color later…

    public enum Color{ Red = 100, Green = 150, Blue = 200, Black = 300}

    This should not be a breaking change.

    I think the code above that "consumes" the enum is perfectly valid, and what I’d expect it to look like. I think my perspective may be skewed, but I’m not sure what you could do to "fix" this other than communicate the "best practices" somehow.

  10. Kreditkarten says:

    Interesting Blog…. Ralf

  11. Douglas McClean says:

    One possibility would be:
    [Closed]
    public enum Gender {
    Male,
    Female
    }

    [Open]
    public enum WindowsVersion {
    Windows95,
    Windows98,
    WindowsME,
    Windows2000,
    WindowsNT4,
    WindowsXP
    }

    To let clients know what you know about whether an enum is likely to be added to. Metadata is a powerful tool, we should probably consider using it more often, even when we don’t have a particular use in mind. In fact, I think I might add an attribute like this to my code sooner than later, maybe write some FxCop rules based on it.

    I agree though, that it’s a terrible practice to assume that the default means the one other case you were too lazy to write. What if the parameter is an undefined value for that enum, for example, since .NET provides no checking that the value in an enum variable will actually be a value of that enum type, only that it will be a value of the underlying valuetype.

  12. I had the same issue when I experimented with <a href="http://ernstkuschke.blogspot.com/#107088160665952783">the idea</a> I had a long while ago of dynamically generating enums from a set of pre-defined values from a DB… Kind of abandoned it after a while!

    -Ernst

  13. Michael says:

    Really helpfull ideas

  14. Kreditkarten says:

    RE: Have you been burned by versioning enums

    I like to assign integer values to my enumerations, borrowing from the old BASIC line numbering notion. public enum Color{ Red = 100, Blue = 200, Black = 300} then, if I add a color later… public enum Color{ Red = 100, Green = 150, Blue = 200, Black = 300} This should not be a breaking change. I think the code above that "consumes" the enum is perfectly valid, and what I’d expect it to look like. I think my perspective may be skewed, but I’m not sure what you could do to "fix" this other than communicate the "best practices" somehow.

  15. Brad Abrams says:

    Kreditkarten, what you are doing is fine, but you don’t need to have the values in any particular order… they don’t need to be in any particular order.

  16. Jack Miller says:

    The assumption lies in coding a default case where you assume you know what the value is: well, if you know, why don’t you test for it explicitly?

  17. Really helpfull ideas ! Many Thanks!