Proposal: sealed enums (completeness checking in switch statements) #782
Replies: 13 comments 6 replies
-
This sounds too restrictive to me. I think you want to prevent mistakes in the common case, where every enum value has a separate case. But I don't see why you should also prohibit more advanced cases, like How much protection will there be from creating a "wrong" value? I assume the compiler would not allow |
Beta Was this translation helpful? Give feedback.
-
(1) I think this should only affect completeness of a switch statement, e.g. [Complete]
enum E { A, B }
int M(E e) {
switch (e) {
case E.A: return 0;
case E.B: return 1;
}
} // no error (CS0161) (2) The (3) I agree that this should emit the default case anyways but perhaps we should add this functionally to struct ADTs so that we don't have to worry about CLR enums accepting any possible value of the underlying type. |
Beta Was this translation helpful? Give feedback.
-
How it will work with serialization? For example:
Now it fails at runtime because of internals of |
Beta Was this translation helpful? Give feedback.
-
I would prefer something more along the lines of D's Makes more sense to me for this sort of restriction to be requested by the consumer, rather than specified on the type itself. |
Beta Was this translation helpful? Give feedback.
-
@YotaXP and what if this
|
Beta Was this translation helpful? Give feedback.
-
@Pzixel was your comment addressed to svick or to me? Because I don't see any switches in your example code. I do not propose to prevent instantiation of invalid enum values in this issue. |
Beta Was this translation helpful? Give feedback.
-
@Pzixel Copying my code is absolutely fine, but converting it to use tabs... tsk tsk. 🤔 |
Beta Was this translation helpful? Give feedback.
-
@orthoxerox I'm just going your way and show that even today we have some problems with invalid enum values and cannot just remove them from the language or add a new feature that works this different. And I was talking with @alrz . |
Beta Was this translation helpful? Give feedback.
-
I also believe the control whether the whole enum should be used is the consumer's responsibility. In my view, such hypothetical |
Beta Was this translation helpful? Give feedback.
-
Ha, this is the reversed proposal for rust-lang/rfcs#2008 😄 |
Beta Was this translation helpful? Give feedback.
-
I would still love to see algebraic data types and real enums in C#, but in the meantime, I created a compiler extension that helps me make sure I'm getting all the cases when I want to: https://github.com/abjennings/EnumAnalyzer It looks for places where I throw an "EnumNotExhaustedException", then checks the surrounding code block to make sure all the defined enum values are mentioned at least once. If not, it gives me a diagnostic error. There are definitely corner cases it won't handle, but for me, it seems a good 90/10 solution (90% of the benefit for 10% of the cost). Also, it's my first compiler extension, so any feedback is appreciated... |
Beta Was this translation helpful? Give feedback.
-
This is still a feature I'd like to have. Currently, with switch expressions, the compiler still complains about non-completeness, and wants a default arm: public enum MyEnum
{
A,
B,
}
// ...
public int GetValue(MyEnum value) =>
value switch
{
MyEnum.A => 0,
MyEnum.B => 1,
// one of the following, depending on desired contract
_ => throw new ArgumentOutOfRangeException(nameof(value), value, $"Unknown enum value of type {nameof(MyEnum)}."),
_ => throw new UnreachableException(),
}; It's not that big of a deal, but it can be annoying sometimes. |
Beta Was this translation helpful? Give feedback.
-
I would argue that requiring the default arm on switch expressions for enums, as it stands today, is actually detrimental in some cases. Specifically, I always have "what if" concerns when you have two different teams or developers working on two different parts of the code, whether internal to a large project or consumption of a library or any other reason. Specifically, imagine this scenario from above: public enum MyEnum
{
A,
B,
}
public int GetValue(MyEnum value) =>
value switch
{
MyEnum.A => 0,
MyEnum.B => 1,
_ => throw new ArgumentOutOfRangeException(nameof(value), value, $"Unknown enum value of type {nameof(MyEnum)}.")
}; However, let's assume that the enumeration and the consuming method are maintained by different developers in different projects. Perhaps the enumeration is part of the .NET runtime. When an upgrade occurs, a new enumeration value is added. public enum MyEnum
{
A,
B,
C,
}
public int GetValue(MyEnum value) =>
value switch
{
MyEnum.A => 0,
MyEnum.B => 1,
_ => throw new ArgumentOutOfRangeException(nameof(value), value, $"Unknown enum value of type {nameof(MyEnum)}.")
}; In this case, because the default arm is present, there is no compiler warning that you have an unhandled case. It would be easy for the developer to move on, thinking everything is fine, but then get unexpected behaviors in production at runtime when the new enumeration value reaches the method. I understand the need for the default arm, because the enumeration is an integer and could be any value, even one not defined on the enumeration. However, it would be nice to support some method to (optionally) still require all enum values to be handled at compile time. Perhaps something like below would give a warning or error: public int GetValue(MyEnum value) =>
value switch
{
MyEnum.A => 0,
MyEnum.B => 1,
checked _ => throw new ArgumentOutOfRangeException(nameof(value), value, $"Unknown enum value of type {nameof(MyEnum)}.")
}; |
Beta Was this translation helpful? Give feedback.
-
Since full-blown discriminated unions are a long way away (they'll come after records and records will come in C#8.0 or later), I propose to implement a much smaller feature that will allow us to avoid incorrect defaults or skipped clauses.
System.SealedEnumerationAttribute
. It is a compile-time error when a single type has bothSystem.SealedEnumerationAttribute
andSystem.FlagsAttribute
.sealed
modifier. This is equivalent to decorating the type withSystem.SealedEnumerationAttribute
.Beta Was this translation helpful? Give feedback.
All reactions