Probably a silly question, since I may have already answered my question, but I just want to be sure that I'm not missing something
Constant expressions are evaluated at compile time within checked context. I thought the following expression shouldn't be evaluated at compile time, since I assumed C# considers a particular expression as a constant expression only if all operands on the left-hand side are constants:
int i= 100;
long u = (int.MaxValue + 100 + i); //error
Instead it appears compiler considers any subexpression where both operands are constants as a constant expression, even if other operands in an expression are non-constants? Thus compiler may only evaluate a part of an expression at compile time, while the remained of the expression ( which contains non-constant values ) will get evaluated at run-time --> I assume in the following example only (200 +100)
gets evaluated at compile time
int i=100;
long l = int.MaxValue + i + ( 200 + 100 ); // works
Are my assumptions correct?
thanx
A constant expression gets evaluated at compile time, not run time, and can be used in any place that a constant can be used. The constant expression must evaluate to a constant that is in the range of representable values for that type.
During compile time the compiler check for the syntax, semantic, and type of the code.
A constexpr function that is eligible to be evaluated at compile-time will only be evaluated at compile-time if the return value is used where a constant expression is required. Otherwise, compile-time evaluation is not guaranteed.
In computing, compile-time function execution (or compile time function evaluation, or general constant expressions) is the ability of a compiler, that would normally compile a function to machine code and execute it at run time, to execute the function at compile time.
doec C# classify any subexpression where both operands are constants as a constant expression, even if other operands in an expression are non-constants?
The answer to your question is "yes," but it is important to clearly understand what constitutes a "subexpression".
I assume in "int.MaxValue + i + ( 200 + 100 )" only (200 + 100) gets evaluated at compile time
Correct. Now, if you had said instead "int.MaxValue + i + 200 + 100" then "200 + 100" would never be evaluated at all because that's not a subexpression due to associativity.
This is a bit subtle. Let me explain in detail.
First off, let's distinguish between de jure compile time constants and de facto compile time constants. Let me give you an example. Consider this method body:
const int x = 123;
int y = x + x;
if (y * 0 != 0) Console.WriteLine("WOO HOO");
M(ref y);
In C# 1 and 2 if you compiled that with optimizations on, that would be compiled as if you had written:
int y = 246;
M(ref y);
The constant x vanishes, y is initialized to the constant-folded expression, and the arithmetic optimizer realizes that any local integer times zero is never not equal to zero, so it optimizes that away as well.
In C# 3 I accidentally introduced a bug in the optimizer which was not fixed in C# 4 either. In C# 3/4 we'd generate that as
int y = 246;
bool b = false;
if (b) Console.WriteLine("WOO HOO");
M(ref y);
That is, the arithmetic is optimized away, but we don't go the step further and optimize away the "if(false)".
The difference is that the constant folding behaviour on x + x is guaranteed to happen at compile time, but the constant folding behaviour on the partially-variable expression y*0 is not.
I regret the error and apologize. However, the reason I changed this in C# 3 and accidentally introduced a bug in the code generator was to fix a bug in the semantic analyzer. In C# 2.0 this was legal:
int z;
int y = 246;
if (y * 0 == 0) z = 123;
Console.WriteLine(z);
That should not be legal. You know and I know that y * 0 == 0 will always be true, and therefore z is assigned before it is read, but the spec says that this analysis must only be performed if the expression in the "if" is a compile-time constant, and that's not a compile-time constant because it contains a variable. We took the breaking change for C# 3.0.
So, OK, let's assume that you understand the difference between a de jure constant that must be evaluated because the spec says so, and a de facto constant that is evaluated because the optimizer is smart. Your question is under what circumstances can an expression be de jure and de facto partially "folded" at compile time? (By "folded" I mean resolving an expression containing constants into a simpler expression.)
The first thing we have to consider is the associativity of the operators. Only once we have done the associativity and precedence analysis do we know what is and is not a subexpression. Consider
int y = 3;
int x = 2 - 1 + y;
Addition and subtraction are left-associative, as are most operators in C#, so that is the same as
int x = (2 - 1) + y;
Now clearly (2 - 1) is a de jure constant expression, so that is folded and becomes 1.
If on the other hand you said
int x = y + 2 - 1;
That is
int x = (y + 2) - 1;
and it is not folded because those are two non-constant expressions.
This could have a real effect in a checked context. If y is int.MaxValue - 1 then the first version will not overflow but the second version will!
Now, the compiler and optimizer are permitted to say "well, I happen to know that this is an unchecked context, and I happen to know that I can turn that into "y + 1" safely, so I will." The C# compiler does not do this but the jitter might. In your particular example,
long l = int.MaxValue + i + 200 + 100 ;
is actually code-gen'd by the C# compiler as
long l = ((int.MaxValue + i) + 200) + 100 ;
And not
long l = (int.MaxValue + i) + 300;
The jitter may choose to make that optimization if it so wishes and can prove that doing so is safe.
But
long l = int.MaxValue + i + (200 + 100);
would of course be generated as
long l = (int.MaxValue + i) + 300;
However, we do perform the optimization you want on strings in the C# compiler! If you say
string y = whatever;
string x = y + "A" + "B" + "C";
then you might think, well, that's a left-associative expression so that's:
string x = ((y + "A") + "B") + "C";
And therefore there will be no constant folding. However, we actually detect this situation and at compile time do the folding to the right, so we generate this as if you'd written
string x = y + "ABC";
Thereby saving the cost of the concatenations at runtime. The string concat optimizer is actually reasonably sophisticated about recognizing various patterns for gluing strings together at compile time.
We could do the same for unchecked arithmetic. We just haven't gotten around to it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With