Introduction
Long time ago I've learned from John Skeet's blog about java enums which are full-fledged classes. This approach have obvious benefits: you can add behavior to enums.
I've found on Stackoverflow solutions how to implement this in C# . Unfortunately all of them haven't worked with switch statement because case requires to be followed by constant value which can't be of reference type (string is the only exception with special support from the compiler).
Things have changed with C# 7.0 with new enhanced switch statement.
Using the code
Basic idea is to change enum to an immutable class and expose static readonly instances of that class.
Most of boilerplate code I've placed in EnumBase
class. This includes serialization support, comparison operators, etc. All my enums inherit from this class.
In EnumBase
class I've assumed that internally enum values are represented by int. This class also supports enum description which in standard enum is usually represented by some custom attribute.
As an example here is TransactionState
class:
public class TransactionState : EnumBase
{
public static readonly TransactionState COMPLETE = new TransactionState(1, "Transaction complete");
public static readonly TransactionState REJECTED = new TransactionState(2, "Transaction rejected");
public static readonly TransactionState PENDING = new TransactionState(3, "Transaction pending");
public static readonly TransactionState AWAITING_APPROVAL = new TransactionState(4, "Transaction awaiting approval");
private TransactionState(int value, string description)
: base(value, description)
{
}
private TransactionState(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
public virtual bool IsTransactionComplete()
{
if (value == 1)
return true;
return false;
}
}
This example is based on java sample from this blog: http://blog.scottlogic.com/2016/07/28/java-enums-how-to-use-them-smarter.html
As you see there is very little boilerplate code - just one additional constructor for proper serialization and another one to initialize values.
IsTransactionComplete
is a method which adds some useful logic inside enum itself: it decides about enum category. With standard enums you would have to implement this in some other helper class.
Now is the time to show usage of this class:
class Program
{
static void Main()
{
TransactionState value = TransactionState.PENDING;
TransactionState secondValue = TransactionState.COMPLETE;
switch (value)
{
case var tsValue when tsValue == TransactionState.COMPLETE && secondValue == TransactionState.AWAITING_APPROVAL:
Console.WriteLine("Y");
break;
case var tsValue when tsValue == TransactionState.PENDING && secondValue == TransactionState.COMPLETE:
Console.WriteLine("Value is PENDING, secondValue is COMPLETE");
break;
case var tsValue when tsValue == TransactionState.REJECTED:
Console.WriteLine("Z");
break;
}
if (value.IsTransactionComplete())
{
Console.WriteLine("Value is COMPLETE");
}
else
{
Console.WriteLine("Value is not COMPLETE");
}
Console.WriteLine("value.Tostring(): {0}", value.ToString());
}
}
I've used new switch form with pattern matching and when keyword.
Conclusion
Since C# 7.0 you can use standard classes in place of enums which can be useful for greater flexibility and usability in more complex cases.