enum class
es are a new kind of type declaration, sometimes referred to as discriminated unions,
where each every possible instance the type is listed, and each instance is non-overlapping.
An enum class
is defined using the following syntax:
enum_class
: 'partial'? 'enum class' identifier type_parameter_list? type_parameter_constraints_clause*
'{' enum_class_body '}'
;
enum_class_body
: enum_class_cases?
| enum_class_cases ','
;
enum_class_cases
: enum_class_case
| enum_class_case ',' enum_class_cases
;
enum_class_case
: enum_class
| class_declaration
| identifier type_parameter_list? '(' formal_parameter_list? ')'
| identifier
;
Sample syntax:
enum class Shape
{
Rectangle(float Width, float Length),
Circle(float Radius),
}
An enum class
definition defines a root type, which is an abstract class of the same name as
the enum class
declaration, and a set of members, each of which has a type which is a subtype
of the root type. If there are multiple partial enum class
definitions, all members will be
considered members of the enum class definition. Unlike a user-defined abstract class definition,
the enum class
root type is partial by default and defined to have a default private
parameter-less constructor.
Note that, since the root type is defined to be a partial abstract class, partial definitions of
the root type may also be added, where standard syntax forms for a class body are allowed.
However, no types may directly inherit from the root type in any declaration, aside from those
specified as enum class
members. In addition, no user-defined constructors are permitted for
the root type.
There are four kinds of enum class
member declarations:
-
simple class members
-
complex class members
-
enum class
members -
value members.
A simple class member declaration defines a new nested "record" class (intentionally left undefined in this document) with the same name. The nested class inherits from the root type.
Given the sample code above,
enum class Shape
{
Rectangle(float Width, float Length),
Circle(float Radius)
}
the enum class
declaration has semantics equivalent to the following declaration
abstract partial class Shape
{
public record Rectangle(float Width, float Length) : Shape;
public record Circle(float Radius) : Shape;
}
You can also nest an entire class declaration below an enum class
declaration. It will be treated as
a nested class of the root type. The syntax allows any class declaration, but it is required for the
complex class member to inherit from the direct containing enum class
declaration.
enum classes
can be nested under each other, e.g.
enum class Expr
{
enum class Binary
{
Addition(Expr left, Expr right),
Multiplication(Expr left, Expr right)
}
}
This is almost identical to the semantics of a top-level enum class
, except that
the nested enum class defines a nested root type, and everything below the nested enum
class is a subtype of the nested root type, instead of the top-level root type.
abstract partial class Expr
{
abstract partial class Binary : Expr
{
public record Addition(Expr left, Expr right) : Binary;
public record Multiplication(Expr left, Expr right) : Binary;
}
}
enum classes
can also contain value members. Value members define public get-only static
properties on the root type that also return the root type, e.g.
enum class Color
{
Red,
Green
}
has properties equivalent to
partial abstract class Color
{
public static Color Red => ...;
public static Color Green => ...;
}
The complete semantics are considered an implementation detail, but it is guaranteed that one unique instance will be returned for each property, and the same instance will be returned on repeated invocations.
There are some proposed adjustments to pattern matching and the switch expression to handle
enum classes
. Switch expressions can already match types through the variable pattern, but
for currently for reference types, no set of switch arms in the switch expression are considered
complete, except for matching against the static type of the argument, or a subtype.
Switch expressions would be changed such that, if the root type of an enum class
is the static
type of the argument to the switch expression, and there is a set of patterns matching all
members of the enum, then the switch will be considered exhaustive.
Since value members are not constants and do not define new static types, they currently cannot
be matched by pattern. To make this possible, a new pattern using the constant pattern syntax
will be added to allow match against enum class
value members. The match is defined to succeed
if and only if the argument to the pattern match and the value returned by the enum class
value
member would be reference equal, although the implementation is not required to perform this
check.
-
What does the common type algorithm say about
enum class
members? Is this valid code?var x = b ? new Shape.Rectangle(...) : new Shape.Circle(...)
-
Adding a new pattern just for value members seems heavy handed. Is there a more general version construction that makes sense?
- Value members also do not map well to a parallel nested class construction because of this
-
Is switching against an argument with an
enum class
static type guaranteed to be constant-time? -
Should there be a way to make
enum class
es not be considered complete in the switch expression? Prefix withvirtual
? -
What modifiers should be permitted on
enum class
?