Fer
Fer

Reputation: 1992

how can i use switch statement on type-safe enum pattern

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.

Upvotes: 19

Views: 10366

Answers (3)

user1542042
user1542042

Reputation: 195

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

    ...
}

Upvotes: 2

Alex Burtsev
Alex Burtsev

Reputation: 12668

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;
    }
}

Upvotes: 10

Jord&#227;o
Jord&#227;o

Reputation: 56477

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.

Upvotes: 9

Related Questions