9

I was recently told that using Enum:

public enum TaskEndState { Error, Completed, Running }

may have compatibility/serialization issues, and thus sometimes it's better to use const string:

public const string TASK_END_STATE = "END_STATE";
public const string TASK_END_STATE_ERROR = "TASK_END_STATE_ERROR";
public const string TASK_END_STATE_COMPLETE = "TASK_END_STATE_COMPLETE";
public const string TASK_END_STATE_RUNNING = "TASK_END_STATE_RUNNING";

Can you find practical use case where it may happen, is there any guidelines where Enum's should be avoided?

Edit: My production environment has multiple WFC services (different versions of the same product). A later version may/or may not include some new properties as Task end state (this is just an example). If we try to deserialize a new Enum value in an older version of a specific service, it may not work.

svick
  • 9,999
  • 1
  • 37
  • 51
Yosi Dahari
  • 602
  • 7
  • 17
  • 3
    You would need to expand on the "compatibility/serialization issues" - this is overly broad otherwise. – Oded Nov 06 '13 at 13:37
  • @Oded - I tried to provide more details. – Yosi Dahari Nov 06 '13 at 13:50
  • 2
    So, this is a serialization & _versioning_ issue? – Oded Nov 06 '13 at 13:52
  • @Oded - Yes, within the context of using `Enum`'s. It sounds like it's familiar to you. – Yosi Dahari Nov 06 '13 at 13:54
  • 1
    I can understand why deserialization of enums may be problematic if you do not readily have the enum type TaskEndState available. Is this what you mean? – Neil Nov 06 '13 at 13:55
  • Yes that is exactly what I mean. – Yosi Dahari Nov 06 '13 at 13:57
  • 3
    Strings don't avoid the versioning problem. Suppose you add "TASK_END_STATE_ESCALATED" a year from now. The old version will have just as much trouble with the new string or the new enum, they will both fail or hit the default case. – cdkMoose Nov 06 '13 at 14:05
  • @cdkMoose - I disagree. An object with a string value, doesn't know "where it came from", and thus the deserialization of `TASK_END_STATE_ESCALATED` will succeed. – Yosi Dahari Nov 06 '13 at 14:08
  • 1
    From a type safety point of view, this is a bad idea. You don't have an infinite number of states corresponding to every single string so don't represent 4 states in a type with hundreds of millions of states. Perhaps writing a function to convert Enum -> String, but why compromise type safety? – daniel gratzer Nov 06 '13 at 15:05
  • 3
    Fix your serialization issues in your serializing code. Don't change all the functions everywhere that use `TaskEndState` as an argument type to a string just because a small bit of code gets confused serializing enums. That just doesn't make sense to me. – Reactgular Nov 06 '13 at 15:43
  • 1
    @yosi, yes the string will deserialize, but the behavior is still unpredictable since the old system still has no idea what it means. You still have a problem with the old version of the software, it is a different problem, but a problem none the less. – cdkMoose Nov 06 '13 at 17:34
  • @cdkMoose - I'm not sure myself what to use and when.. this is the question. – Yosi Dahari Nov 06 '13 at 17:36
  • In the end, you will have potential versioning problems with either solution. It more likely comes down to the implementation details of your system and which problem will be easier to deal with in that implementation. – cdkMoose Nov 06 '13 at 17:38

3 Answers3

6

You don't say how exactly are you serializing the Enum, which is important here. I'm going to assume that you serialize it as the underlying integral value. In that case, your current code indeed has a versioning issue, if you add a new value into the middle of the Enum.

For example, in your current code, Error is 0, Completed is 1 and Running is 2. Now imagine you changed the code to:

public enum TaskEndState { Error, Timeout, Completed, Running }

Suddenly, what you saved as Running (old 2) will be read as Completed (new 2), which is not correct.

The best way to deal with this is to explicitly specify the numeric values for each enum member and never change them. You can add new values laster, but they have to have a new integer value. For example, the original:

public enum TaskEndState { Error = 0, Completed = 1, Running = 2 }

And the modified version:

public enum TaskEndState { Error = 0, Timeout = 3, Completed = 1, Running = 2 }
svick
  • 9,999
  • 1
  • 37
  • 51
  • 1
    Nice point, but the question is about comparison vs. const string. – Yosi Dahari Nov 06 '13 at 15:35
  • @Yosi You say string is better than enum, because of versioning issues. I'm saying you can solve those while still using enum. How does that not answer the question? – svick Nov 06 '13 at 15:37
  • Won't trying to deserialize Timeout = 3 (from the new version of `TaskEndState`) with an old version `TaskEndState` fail? – Yosi Dahari Nov 06 '13 at 15:41
  • @Yosi That depends on the deserializer. In any case, you usually can't expect old code to handle new format correctly. – svick Nov 06 '13 at 15:56
  • 1
    Usually enums are serailized as names, not integers, although you can override this behavior. Example [XmlEnum("0")] Error= 0 if you wanted to emit the integer. – Jon Raynor Nov 06 '13 at 19:48
0

There is no right answer for this question. It boils down to how explicit you want the contract to be.

On one hand, you could send this data simply as an xsd:string element named TaskEndState. Here, the contract take any string in the message. Validation will have to be performed in code to verify the string sent in the message matches a known value in the code. New codes can be added without changing the contract. This is a less explicit contract. This type of contract is more flexible to change, but validation is no longer being performed exclusively at the schema level as some validation will have to occur inside the code.

On the other hand, you could define a restriction (enumeration) on the xsd:string element that will only take in values defined. Validation will occur at the schema level only. New codes can be added, but that changes the contract. This is the explicit contract. This type of contract is less flexible to change, but validation is still performed in one place, at the schema level. In some cases this is desireable because schema validation is performed in centralized locations and the general consensus is that message validation is seprated from actual message processing. But, since the contract has changed, callers will have to re-test and validate that the additional restrictions did not break anything.

In general, I usually go with enums because I like my contracts to be explicit as possible. But, there are no absolutes.

In your case, if new strings are not added that often, I'd go with the enum. If TaskEndState data is very volatile (changes constantly) I would go with the generic string.

There are some good articles online one how to handle the restrictions. Here is one such link:

http://www.xml.com/pub/a/2003/02/05/wxs-enum.html?page=1

Jon Raynor
  • 10,905
  • 29
  • 47
-1

For compatibility management, enum vs string is a red herring -- either your app has some understanding of "data document" schema version or not. If it does, then this is a rather mundane implementation detail. If you don't you've got the same problem either way.

How I'd handle this with WCF is make the public-facing layer a thin wrapper that is feeding data documents with some notation of version into internals that can handle it properly. Loads of ways to get to "handle it properly" and alot of that depends on specifics of your app so I can't give an educated answer to that question here.

Wyatt Barnett
  • 20,685
  • 50
  • 69