This has been a pet peeve of mine since I started using .NET but I was curious in case I was missing something. My code snippet won\'t compile (please forgive the forced nat
For the sake of sharing a quirky idea if nothing else, here goes:
...and since the introduction of the nameof operator you can also use them in switch-cases.
(Not that you couldn't technically do so previously, but it was difficult to make such code readable and refactor friendly.)
public struct MyEnum : IEquatable
{
private readonly string name;
private MyEnum(string name) { name = name; }
public string Name
{
// ensure observable pureness and true valuetype behavior of our enum
get { return name ?? nameof(Bork); } // <- by choosing a default here.
}
// our enum values:
public static readonly MyEnum Bork;
public static readonly MyEnum Foo;
public static readonly MyEnum Bar;
public static readonly MyEnum Bas;
// automatic initialization:
static MyEnum()
{
FieldInfo[] values = typeof(MyEnum).GetFields(BindingFlags.Static | BindingFlags.Public);
foreach (var value in values)
value.SetValue(null, new MyEnum(value.Name));
}
/* don't forget these: */
public override bool Equals(object obj)
{
return obj is MyEnum && Equals((MyEnum)obj);
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
public override string ToString()
{
return Name.ToString();
}
public bool Equals(MyEnum other)
{
return Name.Equals(other.Name);
}
public static bool operator ==(MyEnum left, MyEnum right)
{
return left.Equals(right);
}
public static bool operator !=(MyEnum left, MyEnum right)
{
return !left.Equals(right);
}
}
and use it thusly:
public int Example(MyEnum value)
{
switch(value.Name)
{
default: //case nameof(MyEnum.Bork):
return 0;
case nameof(MyEnum.Foo):
return 1;
case nameof(MyEnum.Bar):
return 2;
case nameof(MyEnum.Bas):
return 3;
}
}
and you would of course call that method like so:
int test = Example(MyEnum.Bar); // returns 2
That we can now easily get the Name is basically just a bonus, and yeah some readers might point out that this is basically a Java enum without the null-case (since it's not a class). And just like in Java you can add whatever extra data and or properties you desire to it, e.g. an ordinal value.
Readability: Check!
Intellisense: Check!
Refactorability: Check!
Is a ValueType: Check!
True enumeration: Check!
...
Is it performant? Compared to native enums; no.
Should you use this? Hmmm....
How important is it for you to have true enumerations so you can getting rid of enum runtime checks and their accompanying exceptions?
I don't know. Can't really answer that for you dear reader; to each their own.
...Actually, as I wrote this I realized it would probably be cleaner to let the struct "wrap" a normal enum. (The static struct fields and the corresponding normal enum mirroring each other with the help of similar reflection as above.) Just never use the normal enum as a parameter and you're good.
Yepp, spent the night testing out my ideas, and I was right: I now have near perfect java-style enums in c#. Usage is clean and performance is improved. Best of all: all the nasty shit is encapsulated in the base-class, your own concrete implementation can be as clean as this:
// example java-style enum:
public sealed class Example : Enumeration, IEnumerationMarker
{
private Example () {}
/// Declare your enum constants here - and document them.
public static readonly Example Foo = new Example ();
public static readonly Example Bar = new Example ();
public static readonly Example Bas = new Example ();
// mirror your declaration here:
public enum Const
{
Foo,
Bar,
Bas,
}
}
This is what you can do:
This is what you must do:
At the moment every invariant above is asserted at type initialization. Might try to tweak it later to see if some of it can be detected at compile-time.
Requirements Rationale:
nameof method shown earlier. It just would not be as performant. I'm still contemplating if a should relax this requirement or not. I'll experiment on it...So anyway, how can you use these java-style enums?
Well I implemented this stuff for now:
int ordinal = Example.Bar.Ordinal; // will be in range: [0, Count-1]
string name = Example.Bas.Name; // "Bas"
int count = Enumeration.Count(); // 3
var value = Example.Foo.Value; // <-- Example.Const.Foo
Example[] values;
Enumeration.Values(out values);
foreach (var value in Enumeration.Values())
Console.WriteLine(value); // "Foo", "Bar", "Bas"
public int Switching(Example value)
{
if (value == null)
return -1;
// since we are switching on a System.Enum tabbing to autocomplete all cases works!
switch (value.Value)
{
case Example.Const.Foo:
return 12345;
case Example.Const.Bar:
return value.GetHasCode();
case Example.Const.Bas:
return value.Ordinal * 42;
default:
return 0;
}
}
The abstract Enumeration class will also implement the IEquatable interface for us, including == and != operators that will work on Example instances.
Aside from the reflection needed during type initialization everything is clean and performant. Will probably move on to implement the specialized collections java has for enums.
So where is this code then?
I want to see if I can clean it up a bit more before I release it, but it will probably be up on a dev branch on GitHub by the end of the week - unless I find other crazy projects to work on! ^_^
Now up on GitHub
See Enumeration.cs and Enumeration_T2.cs.
They are currently part of the dev branch of a very much wip library I'm working on.
(Nothing is "releasable" yet and subject to breaking changes at any moment.)
...For now the rest of the library is mostly a shit ton of boilerplate to extend all array methods to multi-rank arrays, make multi-rank arrays usable with Linq, and performant ReadOnlyArray wrappers (immutable structs) for exposing (private) arrays in a safe way without the cringy need to create copies all the time.
Everything* except the very latest dev commits is fully documented and IntelliSense friendly.
(*The java enum types are still wip and will be properly documented once I've finalized their design.)