The C# compiler is a bit ... old fashioned ... and won't do static analysis. So it breaks on seemingly correct code like this:
MyStruct s;
bool inited = false;
foreach( Something foo in ACollection )
{
if( foo.Test() )
continue;
if( inited )
s.DoSomething();
else
{
s = foo.GetMeAnS();
inited = true;
}
}
Note: the unusual problem is that "s" is a struct. If it were a class, I'd simply init it to null. This struct has no meaningful "uninited" state, and I don't want to pay the performance cost of initing something I immediately throw away, just to satisfy a weak compiler.
The code (should be) fully correct: it's impossible to access s until s has been inited. (I've copy/pasted from actual code, but edited-out long method names for simplicity).
C# compiler in Mono used to allow this, but now it doesn't. Nothing has changed except the compiler, which now gives an error on unassigned variable.
Is there a code way to tell it to shut up and mind its own business? :) I don't want to fiddle with changing compiler-settings (if possible) because the code is compiled by other people/orgs - I'd prefer a code way of fixing the problem.
Is there a code way to tell it to shut up and mind its own business?
The compiler's business is implementing the C# specification. The code you've written should not compile according to the C# specification. The s.DoSomething()
call is reachable without s
being definitely assigned, therefore your code is broken. That's not the compiler's fault. If the Mono compiler used to allow it, that was a bug which has apparently now been fixed.
The simplest way of fixing it is to definitely assign the value, of course:
MyStruct s = new MyStruct(); // Value will never actually be used
There are plenty of cases where we (as humans) can tell that something will never happen, but the compiler can't. Here's another example:
public int Foo(int input)
{
if (input >= 0)
{
return input;
}
else if (input < 0)
{
return -input;
}
// This is still reachable...
}
We know that every int
input will go into one of those if
bodies, but the compiler will still (correctly) give a compilation error on the above code, because the closing brace is reachable and it's a non-void method.
Your claim that "The code (should be) fully correct" is according to your reasoning, not the C# specificiation... and the compiler is only meant to care about the latter.
One thing to note: the specification doesn't even care about the fact that we do actually set inited
to true in some cases. Even if it always had the value of false
, it's still just a local variable, not a constant expression. Here's a simple example demonstrating that with no loop:
static void Main()
{
int x;
bool condition = false;
if (condition)
{
Console.WriteLine(x);
}
}
This still gives an error: "error CS0165: Use of unassigned local variable 'x'"
From section 8.7.1 of the C# 5 specification:
The first embedded statement of an if
statement is reachable if the if statement is reachable and the boolean expression does not have the constant value false
.
Here the expression is condition
, which is a local variable. A local variable is not a constant expression in technical terms, even if it will never change. If you make it a local constant instead, it will compile:
static void Main()
{
int x;
const bool condition = false;
if (condition)
{
Console.WriteLine(x);
}
}
Now there's a warning about the body of the if
statement being unreachable - but there's no error.
The C# specification was designed such that you should never be able to use an uninitialized variable. This is not an old fashioned concept. The old fashioned way to deal with this (in C++) was to say, "This is undefined behaviour, anything can happen".
Strangely enough lots of bugs were caused by this attitude, which gave rise to many compilers automagically initing (in debug mode) variables into such gems as 0xDEADBEEF.
As for why the C# compiler doesn't do code analysis to find if the variable is inited when it reaches that code?
Halting Problem
The problem could be rewritten like this.
bool inited = false;
MyStruct? s;
while (true)
{
foreach( Something foo in ACollection )
foo.Test();
if( inited && false == s.HasValue )
return;
}
This is only slightly changed code. But you can see, I have converted your problem into the Halting Problem, where the inputs are, the state of each Something
and the implementation of foo.Test()
.
This is proven to be undecidable on a Turing Machine, which your CLR VM certainly is.
In short, you are asking, why hasn't microsoft broken the laws of Mathematics and Computer Science when they wrote the C# compiler.
Or you are asking, shouldn't the Microsoft C# Compiler try hard before giving up on the Halting Problem. To which my response is, how hard should they try?