Emulate Java Enums With Values in C#

Update August 28, 2021

I have written a follow-up that improves on the following solution using a type conversion operator overload.

When I started dabbling in C#, I wondered if it supports values in enums. In Java, an enum instance can have properties (called fields in Java lingo) associated with the enum’s literals. By taking advantage of this feature, you can encode more information in an enum, like a string, for example, or a constant number. You can even embed instantiated class objects, maybe to associate an object factory with a literal.

In my use case, I wanted to achieve a form of a key-value-pair mapping. I require certain illegal characters in the NTFS file or directory names to be replaced with a given code. I use HTML encoding for my needs because I can simply look up the values online if I need to.

Here is the Java reference example. First, let me start with the basic enum definition (I use Lombok to auto-generate boilerplate code like the constructor and accessors).

@Getter
@RequiredArgsConstructor
enum CharacterReplacementCode  {
    COLON(":", "&58;"),
    POUND("#", "&35;"),
    QUESTION_MARK("?", "&63;"); 

    private final String character;
    private final String replacement;
    
    @Override
    public String toString() {
        return String.format("Character '%s' substituted by code '%s'", character, replacement); 
    }
}

Usage of that enum is as you would expect. You can access the literals by prefixing them with the enum name: ˋvar colon = CharacterReplacementCode.COLONˋ. You can get to the fields through the variable by using the generated accessor: ˋcolon.getCharacter()ˋ.

There is nothing you could not achieve with a simple class and a few constants until this point. The interesting part happens when you want to get a list of all enum literals and use the value in a switch statement.

Stream.of(CharacterReplacementCode.values()).forEach(code -> {
    switch (code) {
        case COLON:
            System.out.println("COLON - " + code);
            break;
        case POUND:
            System.out.println("POUND - " + code);
            break;
        case QUESTION_MARK:
            System.out.println("QUESTION - " + code);
            break;
    }
});

Being a regular enum, switching is easy. Now, how would you do that in C#?

In C#, this becomes a bit more convoluted, but it is still achievable. First, let me start with the class definition that is supposed to emulate the enum.

public sealed class CharacterReplacementCode
{
    public static readonly CharacterReplacementCode Colon = new CharacterReplacementCode(":", "&58;");
    public static readonly CharacterReplacementCode Pound = new CharacterReplacementCode("#", "&35;");
    public static readonly CharacterReplacementCode QuestionMark = new CharacterReplacementCode("?", "&63;");

    public string Character { get; private set; }
    public string Replacement { get; private set; }

    CharacterReplacementCode(string character, string code)
        => (Character, Replacement) = (character, code);

    public override string ToString()
    {
        return $"Character '{Character}' substituted by code '{Replacement}'";
    }
}

With this construct, I managed to jump the first hurdle, the small one, where I defined static names and associated them with some values. The usage is similar to the Java version, only with C# syntax.

var colon = CharacterReplacementCode.Colon;
colon.Character;

What about iterating all "literals" or constants? I jumped the second hurdle by utilizing the ˋyieldˋ keyword. Here is a link to a YouTube video explaining the concept.

public sealed class CharacterReplacementCode
{
    ...
    public static IEnumerable Values 
    {
        get
        {
            yield return Pound;
            yield return Colon;
            yield return QuestionMark;
        }
    }
    ...
}

By adding the ˋValuesˋ property, the CharacterReplacementCode class’ constants can now be iterated in a traditional ˋforeachˋ loop.

foreach (var code in CharacterReplacementCode.Values) {
    ...
}

The final missing piece is the ˋswitchˋ statement, and this is where it gets a bit unwieldy. So far, all that was necessary was a bit of boilerplate code. Unlike in Java, though, where we deal with an enum and get some special treatment, in C#, we now have a class instance instead. This is where C# Pattern Matching comes to the rescue. It is not as pretty as in Java, but it gets the job done. Here is the complete loop.

foreach (var code in CharacterReplacementCode.Values) { 
    switch (code) { 
        case CharacterReplacementCode { Character: ":"  }:
            Console.WriteLine("COLON: " + code);
            break; 
        case CharacterReplacementCode { Character: "#" }: 
            Console.WriteLine("POUND: " + code); 
            break; 
        case CharacterReplacementCode { Character: "?" }: 
            Console.WriteLine("QUESTION: " + code); 
        break;
    }
} 

As you can see, it is not as intuitive as the Java reference code. I had to specify the object type and the property by which I identify my "literal". Remember, it is nothing more than a static class instance variable. You can do even more with this feature, depending on how complex your objects are. It can even operate on the base ˋobjectˋ type, and you can pick and choose which concrete implementation to compare with.

Here are a few links that describe the concept in more detail. This is a topic of its own, so I will not cover it in depth myself in this blog post.

One thought on “Emulate Java Enums With Values in C#

Leave a Reply to Emulate Java Enums With Values in C# (Pt. 2, Improved With Conversion Operator Overload) – The Codeslinger Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.