18

I found a goodlooking example about implementation enums in a different way. That is called type-safe enum pattern i think. I started using it but i realized that i can not use it in a switch statement.
My implementation looks like the following:

public sealed class MyState
{
    private readonly string m_Name;
    private readonly int m_Value;

    public static readonly MyState PASSED= new MyState(1, "OK");
    public static readonly MyState FAILED= new MyState(2, "ERROR");

    private MyState(int value, string name)
    {
        m_Name = name;
        m_Value = value;
    }

    public override string ToString()
    {
        return m_Name;
    }

    public int GetIntValue()
    {
        return m_Value;
    }
}

What can i add to my class in order to be able to use this pattern in switch statements in C#?
Thanks.

Fer
  • 1,962
  • 7
  • 29
  • 58
  • 2
    What is insufficiently "type-safe" about the standard enum implementation? – Cody Gray - on strike Apr 11 '12 at 05:33
  • GetIntValue should be a property called `Value` or `ID`. You can switch that property – Gilad Naaman Apr 11 '12 at 05:35
  • Agree with @CodyGray, the whole point of enums is type safety. – harpo Apr 11 '12 at 05:36
  • Well, maybe i shouldnt call it as type-safed. does it have a name as a design pattern? And, my purpose is not performing type safety. Actually my purpose is designing an extansible custom enum class. What could you profer? And how can i use switch statement in this synerio? – Fer Apr 11 '12 at 05:43
  • Just use [a standard enumeration](http://msdn.microsoft.com/en-us/library/sbbt4032.aspx). Yes, you can switch on that. – Cody Gray - on strike Apr 11 '12 at 05:47
  • 1
    @CodyGray refer to this question: http://stackoverflow.com/questions/424366/c-sharp-string-enums . i took the implementation from the answer of this question. And i thought it is like a standart design pattern for enums. Well, i think i will have to think an other way to do my goal. Thanks. – Fer Apr 11 '12 at 05:56
  • 4
    @Fer your class usage instead of enums is absolutely correct. I never use enums in C#, for many reasons. – Alex Burtsev Apr 11 '12 at 16:43
  • @Cody Gray: enums are not really type-safe if you consider the enum members themselves: with `enum E { One = 1, Two = 2 }` I can do `E three = (E)3;`. They're only type-safe with regards to their underlying type (`int` by default). – Jordão Apr 11 '12 at 16:53
  • @Jordão: Casting is, by definition, working around type safety. – Cody Gray - on strike Apr 11 '12 at 16:56
  • @Cody Gray: But the casting that I showed works! That means conceptually something like "3 is an instance of E", even though you didn't intend it to be... – Jordão Apr 11 '12 at 17:04
  • @Jordão: Yes, it "works", whatever that means. What would you expect it to do? Throw an exception? With a cast, you've essentially told the compiler *not* to enforce type safety, that you know what you're doing and it should let you do it, even it doesn't make any sense. Yes, it should probably produce a compiler warning, but it doesn't make sense for this to be expressly forbidden, as there are use cases for it. Obviously there are alternatives, my point was just that the a standard `enum` should be sufficiently type-safe in 99% of situations. Casting like that would be a bug in the caller. – Cody Gray - on strike Apr 11 '12 at 17:12
  • @Cody Gray: I agree that the standard enum should be used by default. The type-safe enum pattern comes from Java (pre-enums) where its "type-safe" designation made sense. But it's still useful in C# when you want to attach behavior to your enum members. – Jordão Apr 11 '12 at 17:25
  • the problem with Enums not only in type safety, there are databinding problems in XAML, persistent problems in Nhibernate, and others. – Alex Burtsev Apr 11 '12 at 17:28
  • Maybe we should just call it "Instance-based enum pattern" in C#. – Jordão Apr 11 '12 at 17:39
  • @Cody Gray: look [here](http://stackoverflow.com/questions/6413804/why-does-casting-int-to-invalid-enum-value-not-throw-exception) for some discussions on the design of enums in C#. – Jordão Apr 11 '12 at 18:51

3 Answers3

10

You can try something like this:

class Program
{
    static void Main(string[] args)
    {
        Gender gender = Gender.Unknown;

        switch (gender)
        {
            case Gender.Enum.Male:
                break;
            case Gender.Enum.Female:
                break;
            case Gender.Enum.Unknown:
                break;
        }
    }
}

public class Gender : NameValue
{
    private Gender(int value, string name)
        : base(value, name)
    {
    }

    public static readonly Gender Unknown = new Gender(Enum.Unknown, "Unknown");
    public static readonly Gender Male = new Gender(Enum.Male, "Male");
    public static readonly Gender Female = new Gender(Enum.Female, "Female");
    public class Enum
    {
        public const int Unknown = -1;
        public const int Male = 1;
        public const int Female = 2;
    }

}

public abstract class NameValue
{
    private readonly int _value;
    private readonly string _name;

    protected NameValue(int value, string name)
    {
        _value = value;
        _name = name;
    }

    public int Value
    {
        get { return _value; }
    }

    public string Name
    {
        get { return _name; }
    }

    public override string ToString()
    {
        return Name;
    }
    public override int GetHashCode()
    {
        return Value.GetHashCode();
    }

    public override bool Equals(object obj)
    {
        NameValue other = obj as NameValue;
        if (ReferenceEquals(other, null)) return false;
        return this.Value == other.Value;
    }

    public static implicit operator int(NameValue nameValue)
    {
        return nameValue.Value;
    }
}
Alex Burtsev
  • 12,418
  • 8
  • 60
  • 87
  • 2
    This is a bit clumsy, but it does work. The only problem I'm having is the additional nested class with constants that I don't like, but has to be used in order to provide constant values in `switch` statement cases. Any other way maybe? – Robert Koritnik Jun 19 '12 at 14:50
9

The type-safe enum pattern is interesting because you can add behavior to individual enum members (which are instances). So, if the behavior you want to switch-on could be part of the class, just use polymorphism. Note that you might need to create subclasses for each member that overrides the behavior:

public class MyState {

  public static readonly MyState Passed = new MyStatePassed();
  public static readonly MyState Failed = new MyStateFailed();

  public virtual void SomeLogic() {
    // default logic, or make it abstract
  }

  class MyStatePassed : MyState {
    public MyStatePassed() : base(1, "OK") { }
  }
  class MyStateFailed : MyState {
    public MyStateFailed() : base(2, "Error") { }
    public override void SomeLogic() { 
      // Error specific logic!
    }
  }

  ...
}

Usage:

MyState state = ...
state.someLogic();

Now, if the logic clearly doesn't belong and you really want to switch, my advice is to create a sibling enum:

public enum MyStateValue { 
  Passed = 1, Failed = 2
}
public sealed class MyState {
  public static readonly MyState Passed = new MyState(MyStateValue.Passed, "OK");
  public static readonly MyState Failed = new MyState(MyStateValue.Failed, "Error");

  public MyStateValue Value { get; private set; }

  private MyState(MyStateValue value, string name) {
    ...
  }
}

And switch on that:

switch (state.Value) {
  case MyStateValue.Passed: ...
  case MyStateValue.Failed: ...
}

In this case, if the type-safe enum class doesn't have any behavior, there's not much reason for it to exist in place of the enum itself. But of course, you can have logic and a sibling enum at the same time.

Jordão
  • 55,340
  • 13
  • 112
  • 144
  • 1
    thank you very much for your interest. i tried your second choise and it works(creating sibling enums.) For now, the answer of @AlexBurtsev seems more generic for my stuation. but i will keep in my your solution too. Thank you. it really helped – Fer Apr 12 '12 at 05:48
2

Jordão has the right idea, but there is a better way to implement the polymorphism, use delegate.

The use of delegates is faster than a switch statement. (In fact, I am a strong believer that the only place for switch statements in object-oriented development is in a factory method. I always look for some sort of polymorphism to replace any switch statements in any code i deal with.)

For example, if you want a specific behavior based on a type-safe-enum, the following pattern is what I use:

public sealed class EnumExample
{
    #region Delegate definitions
    /// <summary>
    /// This is an example of adding a method to the enum. 
    /// This delegate provides the signature of the method.
    /// </summary>
    /// <param name="input">A parameter for the delegate</param>
    /// <returns>Specifies the return value, in this case a (possibly 
    /// different) EnumExample</returns>
    private delegate EnumExample DoAction(string input);
    #endregion

    #region Enum instances
    /// <summary>
    /// Description of the element
    /// The static readonly makes sure that there is only one immutable 
    /// instance of each.
    /// </summary>
    public static readonly EnumExample FIRST = new EnumExample(1,
        "Name of first value",    
        delegate(string input)
           {
               // do something with input to figure out what state comes next
               return result;
           }
    );
    ...
    #endregion

    #region Private members
    /// <summary>
    /// The string name of the enum
    /// </summary>
    private readonly string name;
    /// <summary>
    /// The integer ID of the enum
    /// </summary>
    private readonly int value;
    /// <summary>
    /// The method that is used to execute Act for this instance
    /// </summary>
    private readonly DoAction action;
    #endregion

    #region Constructors
    /// <summary>
    /// This constructor uses the default value for the action method
    /// 
    /// Note all constructors are private to prevent creation of instances 
    /// by any other code
    /// </summary>
    /// <param name="value">integer id for the enum</param>
    /// <param name="name">string value for the enum</param>
    private EnumExample(int value, string name) 
            : this (value, name, defaultAction)
    {
    }

    /// <summary>
    /// This constructor sets all the values for a single instance.
    /// All constructors should end up calling this one.
    /// </summary>
    /// <param name="value">the integer ID for the enum</param>
    /// <param name="name">the string value of the enum</param>
    /// <param name="action">the method used to Act</param>
    private EnumExample(int value, string name, DoAction action)
    {
        this.name = name;
        this.value = value;
        this.action = action;
    }
    #endregion

    #region Default actions
    /// <summary>
    /// This is the default action for the DoAction delegate
    /// </summary>
    /// <param name="input">The inpute for the action</param>
    /// <returns>The next Enum after the action</returns>
    static private EnumExample defaultAction(string input)
    {
        return FIRST;
    }
    #endregion

    ...
}
Marcus Mangelsdorf
  • 2,852
  • 1
  • 30
  • 40
user1542042
  • 195
  • 1
  • 9