At first glance it would seem that the compiler just can't see that TryDequeue is guaranteed to have set
Oh, it almost certainly can. This is the sort of expression where the C# compiler will omit needless checks, so if you did something valid after foo && then the compiler would compile it as if that foo && wasn't there. Quite likely as if foo wasn't there at all, in fact (in a release build, anyway).
But that's at the level of the compiler, and the bug in your code is at the level of the language.
Consider, the fact that it's possible to cut out foo isn't a feature of the language, it's an optimisation based on the end result.
As far as the C# rules of the language go, foo is a variable. As a variable, it's value may vary. As it may vary, the branch that depends on it may or may not be taken.
Indeed, it's not completely impossible that it could change, if it was captured into an asynchronously executing piece of code, for example.
Yeah, you could say "but it's not captured into an asynchronously executing piece of code", but you have to look at more than just that bit of code to know that.
Programming languages are for people first, computers second. It should be possible to look at a piece of code and know what it means, and the code here means an unassigned variable is possibly accessed. It's great for a clever compiler to change how a program runs, but not what it means: Indeed, that in itself is a test of a bad optimisation, if it changes the meaning it's not an optimisation, it's a bug.
Consider also if this was allowed. Would it be allowed if foo was assigned the value of comparing calculating the factorial of 23 and seeing if it was greater than 2 to the power of 45? That's always true, so should it be considered that a branch after foo && is always taken? If we change the rule from "there must be no branches that can be followed that leave the variable unassigned" to "there must be no branches that can be followed that leave the variable unassigned, being smart about examining those branches", then what level of "smart" should we have? How are we to express that level of smart so that a programmer can know what it is?
Taken to its logical conclusion, we'd have to allow that a C# program would be valid when compiled with one compiler but invalid when compiled with a less smart compiler. And the C# isn't a single programming language any more, but a family of mostly-but-not-entirely-compatible dialects. That's to be avoided if possible.
Even if all the compilers were that smart, we don't want them to be too much smarter than we are in this regard (though we certainly do in some others). If a person can't know themselves if something is valid, how can the write a program?
In all it makes sense that at compile-time only constant expressions can be considered in this regard.
For which reason, if you change foo to be const bool foo = true; then it will indeed compile.
dataQueue.TryPeek(out data)always executed and assigndata. In first snippeddataQueue.TryPeek(out data)will not be executed whenfooisfalse, so it possible (from compiler point of view) thatdataremains unassigned.purgeDataassignment in the first case is moved into the if clause then there is no compiler error either. Put another way, elimination of thepurgedDatavariable resolves the issue as well.