A customer brought to my attention the other day that the C# 2.0 beta release has a breaking change from the previous release. Namely, this code
enum E : byte { A = 1, B = 2};
// . . .
E a = E.A;E b = E.B;int j = a - b;
sets
First off, let me say that we regret the breaking change. We agonize over all breaking changes because we know the pain that they cause customers. We also regret introducing the bug in the first place, thereby forcing us to choose between continuing to be in violation of the specification and breaking existing code. Sorry about all that.
Second, I should describe why exactly the original behaviour is in violation of the C# specification. It's pretty straightforward. Start with section 7.7.5:
Every enumeration type implicitly provides the following predefined operator, where E is the enum type, and U is the underlying type of E: U operator –(E x, E y); This operator is evaluated exactly as (U)((U)x – (U)y)
That clearly means that the assignment above should have the same semantics as
int j = (byte)((byte)a-(byte)b));
C# defines only four built-in subtraction operators:
int operator –(int x, int y);uint operator –(uint x, uint y);long operator –(long x, long y);ulong operator –(ulong x, ulong y);
There is an implicit conversion from
int j = (byte)((int)(byte)a-(int)(byte)b));
The conversions from E to byte to int will go off without a hitch, and the subtraction will result in an int set to -1. That then gets cast to byte. What happens when we try to cast a computed-at-runtime integer to a byte? Section 7.5.12 says
For non-constant expressions (expressions that are evaluated at run-time) that are not enclosed by any
Therefore this is an unchecked cast, and -1 goes to 255 as a
Third, I should talk a bit about the process we go through when making breaking changes like this. The change was made to the C# 2.0 compiler on the 14th of January 2004, six months before beta 1, and one of the reasons we try to push betas out really early is to get feedback on whether breaking changes like this affect millions, thousands, or dozens of people. Since to my knowledge the first customer to run into a break contacted us this week, and we're only taking "the product electrocutes millions of users" bug fixes right now, unfortunately this one does not make the bar for choosing backwards compatibility over correctness. I feel bad about that, but I hope you understand our reasoning here. We've got to ship this thing! We'll also make sure that a Knowledge Base article describing the problem gets written.
We on the C# team hate making breaking changes. As my colleague Neal called out in his article on the