Enumerated types
Enumerated (discrete) types are a powerful modeling tool for software developers: they allows them to explicitly state all and only the permitted values a variable can hold, with guarantee that
- no invalid values can be pushed into a function, and
- conditional (switch/case or pattern-matching based) depending on enumerated types can be recognized to be exhaustive by compilers.
This is strictly true for enums you can define in languages like Scala (case class
es), Kotlin (enum
s) or even the old, mistreated Java (enum
s), but is only an unmaintained, misleading promise for C#’s enum
s.
The problem (or “The C# way” to enum
s”)
Defining an enum
in C# indeed is only a syntactic sugar you can leverage to define related, “namespaced” integer constants:
1 | public enum Ordinal { |
is in essence only a shortcut for
1 | public static class Ordinal { |
I’m not saying the compiler produces the same output - I’m saying in both cases you can refer to something like Ordinal.Second
in order to get an int
constant whose value is 2
.
Issue #1
No way to define a method, say
1 | void DoSomething(Ordinal o) { |
preventing callers to pass invalid values into:
1 | DoSomething(Ordinal.First); |
is definitely valid code (from the compiler’s point of view) producing the following output:
1 | Ordinal value is 1 |
Issue #2
No way to rely on compiler in order to check exhaustiveness of conditional checks: you can indeed write
1 | public int Foo(Ordinal o) => o switch { |
but the compilers gives you a warning like The switch expression does not handle all possible inputs (is is not exhaustive), even if all values defined by the enum
are explicitly treated; in order to avoid this inappropriate warning you must add a fourth, never used branch to the switch
:
1 | _ => throw new Exception("Unexpected value") |
(or you can return a special value, if you like code smells ;-)…).
So, C#’s enum
s are syntactic sugar for int
constants, and defining a method parameter of type Ordinal
is nothing different from defining it of type int
(yes, you can define an enum
having byte
or long
or ${other integral type} as underlying representation (see here), but… you got the idea).
From a modelling point of view, C#’s enum
s are a very poor feature, which does not allow developers to define true enumerated (discrete) types: so… please, don’t use them, or at least don’t use them as if they were.
The right way
The right way to model enumerated/discrete types in C# is imho to adopt a pattern that I first heard about in 2004, reading Hardcore Java enlightening book:
1 | public sealed class Ordinal { |
This does not solve the problem of exhaustiveness’ check, but models true discrete type allowing only intended values to be used where Ordinal
parameters are required.
A variant of this pattern, based on inheritance, allow developers to attach polymorphic behaviours to enumeration cases.
Bonus (or “The Java way to enum
s”)
By the way, this is the way enum
‘s implementation in Java (since 2004!!!) and Kotlin work: they provide substantially a syntactic sugar for the pattern above, allowing true enumerated/discrete types modelling:
1 | public enum Ordinal { |
Java supports exhaustiveness check out of the box, too:
1 | void doSomething(Ordinal o) { |