HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Enum wrapped in a struct

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
enumstructwrapped

Problem

I was thinking about how I was going to use some enums I made, how I was going to make sure they had valid values and how to call the class which would contain the necessary extensions methods. And then I thought: why not wrap the enum in a struct. I wrote up a little example code and it looks like this (ignore the fact that it's a very common day enum, I needed something as an example):

public struct Day
{
    private enum DayE
    {
        Monday = 1,
        Tuesday,
        Wednesday,
        Thursday,
        Friday
    }

    private readonly DayE value;

    public static Day Monday { get { return new Day( DayE.Monday ); } }
    public static Day Tuesday { get { return new Day( DayE.Tuesday ); } }
    public static Day Wednesday { get { return new Day( DayE.Wednesday ); } }
    public static Day Thursday { get { return new Day( DayE.Thursday ); } }
    public static Day Friday { get { return new Day( DayE.Friday ); } }

    private Day( DayE value )
    {
        this.value = value;
    }

    public override string ToString()
    {
        switch( value )
        {
            case DayE.Monday:
                return "mo";
            case DayE.Tuesday:
                return "tu";
            case DayE.Wednesday:
                return "we";
            case DayE.Thursday:
                return "th";
            case DayE.Friday:
                return "fr";
            default:
                throw new InvalidOperationException( "Can't convert 0 value" );
        }
    }
}


Some advantages of this approach I could think of:

  • Allows you to limit the possible values, only the ones you construct using the private constructor can be made. (Of course, the 0 value can always be constructed using default(Day) or new Day().) This means you only have to test if the value is not 0 to make sure it's valid. If 0 is a valid value, you don't have to test for validity.



  • No need for an helper class for the extension methods, you can just put them in th

Solution

To use static instances of value types instead of plain enum isn't a strange thing. I can't discuss its merits because each case is different (for sure it's slightly slower but you will hardly notice that) but you can see it often (also in .NET Framework code.) Use it cum grano salis because IMO there aren't many use cases unless you want to emulate more powerful Java's enum.

What's wrong, in my opinion, is that you're mixing too many responsibilities inside the same class. Note that you also have code for representation (abbreviated enum value) which usually doesn't belong to domain.

Second point is to wrap the enum. If you use this pattern then you don't need an enum any more, just drop it and integer value (day of week) will have its own clear name.

Third point is initialization, you don't need a property, because Day is an immutable value type your code may be simplified to a static readonly field.

Let's imagine to declare your struct like this:

public struct Day
{
    public static readonly Day Monday = new Day(1);
    public static readonly Day Tuesday = new Day(2);

    private Day(int dayOfWeek)
    {
        _dayOfWeek = dayOfWeek;
    }

    private readonly int _dayOfWeek;
}


Now we have to make it easy to use during our debugging sessions (and integer value isn't so friendly). We have two options here, adding another field with long name (which eventually might hold also the short one) like this:

public static readonly Day Monday = new Day(nameof(Monday), 1);


Note that the main point is to keep everything in one single point. To add another day (!), for example Sunday, you need to change only one point in your code.

If you need it only for debugging then I'd avoid to override ToString() and I'd go with DebuggerDisplayAttribute but it's a matter of preference. ToString() is sometimes the mandatory choice for type conversion in many poor-designed libraries and I'd avoid to use it as debugging aid when I have better options.

[DebuggerDisplay("{_name}")]
public struct Day { /* */ }


Second option is to write a type ad-hoc for debugging purposes without polluting your value type with unused and heavy strings. Let's introduce DebuggerTypeProxyAttribute:

[DebuggerTypeProxy(typeof(DayDebugView))]
public struct Day { /* */ }


In DayDebugView you can put everything you need for debugging:

internal sealed class DayDebugView
{
    public DayDebugView(Day day)
    {
        _day;
    }

    public string Name
    {
        get
        {
            // Pseudo-code with some Reflection Magic
            // To get the static field name...
            return FindStaticFieldByValue(typeof(Day), _day)?.Name;
        }
    }

    private readonly Day _day;
}


Now you're done (in one way or another) with your debugging aid. If you need a presentation value then get rid of those hard-coded strings (because they're not localization friendly). Note that you might want, it depends on your usage pattern, use/implement IFormatProvider (in a separate class), implement IFormattable, implement a TypeConverter (in a separate class), implement IConvertible or use ValueConverterAttribute and implement IValueConverter. Which one is better/required depends on your UI requirements. For UI ToString() is just an abused handy method...

Add to all the above also code for comparison (at least overriding Equals() and implementing IEquatable but probably also == and !=), eventually type conversions (to/from integers?), parsing to/from strings, serialization and...do you also need flags? Then few test to exercise you did everything correctly and you will ask yourself why you're not using a simple enum (with few lines of validation when it's an input.) Yes, sometimes you will need this structure but less often than you might imagine.

Code Snippets

public struct Day
{
    public static readonly Day Monday = new Day(1);
    public static readonly Day Tuesday = new Day(2);

    private Day(int dayOfWeek)
    {
        _dayOfWeek = dayOfWeek;
    }

    private readonly int _dayOfWeek;
}
public static readonly Day Monday = new Day(nameof(Monday), 1);
[DebuggerDisplay("{_name}")]
public struct Day { /* */ }
[DebuggerTypeProxy(typeof(DayDebugView))]
public struct Day { /* */ }
internal sealed class DayDebugView
{
    public DayDebugView(Day day)
    {
        _day;
    }

    public string Name
    {
        get
        {
            // Pseudo-code with some Reflection Magic
            // To get the static field name...
            return FindStaticFieldByValue(typeof(Day), _day)?.Name;
        }
    }

    private readonly Day _day;
}

Context

StackExchange Code Review Q#154956, answer score: 4

Revisions (0)

No revisions yet.