Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compile time constants and reference types

Tags:

c#

Ok, consider the following code:

    const bool trueOrFalse = false;
    const object constObject = null;

    void Foo()
    {
        if (trueOrFalse)
        {
            int hash = constObject.GetHashCode();
        }

        if (constObject != null)
        {
            int hash = constObject.GetHashCode();
        }
    }

trueOrFalse is a compile time constant and as such, the compiler warns correctly that int hash = o.GetHashCode(); is not reachable.

Also, constObject is a compile time constant and as such the compiler warns again correctly that int hash = o.GetHashCode(); is not reachable as o != null will never be true.

So why doesn't the compiler figure out that:

    if (true)
    {
        int hash = constObject.GetHashCode();
    }

is 100% sure to be a runtime exception and thus issue out a compile time error? I know this is probably a stupid corner case, but the compiler seems pretty smart reasoning about compile time constant value types, and as such, I was expecting it could also figure out this small corner case with reference types.

like image 947
InBetween Avatar asked Jan 23 '12 17:01

InBetween


Video Answer


2 Answers

UPDATE: This question was the subject of my blog on July 17th 2012. Thanks for the great question!

Why doesn't the compiler figure out that my code is 100% sure to be a runtime exception and thus issue out a compile time error?

Why should the compiler make code that is guaranteed to throw into a compile-time error? Wouldn't that make:

int M()
{
    throw new NotImplementedException();
}

into a compile-time error? But that's exactly the opposite of what you want it to be; you want this to be a runtime error so that the incomplete code compiles.

Now, you might say, well, dereferencing null is clearly undesirable always, whereas a "not implemented" exception is clearly desirable. So could the compiler detect just this specific situation of there being a null ref exception guaranteed to happen, and give an error?

Sure, it could. We'd just have to spend the budget on implementing a data flow analyzer that tracks when a given expression is known to be always null, and then make it a compile time error (or warning) to dereference that expression.

The questions to answer then are:

  • How much does that feature cost?
  • How much benefit does the user accrue?
  • Is there any other possible feature that has a better cost-to-benefit ratio, and provides more value to the user?

The answer to the first question is "rather a lot" -- code flow analyzers are expensive to design and build. The answer to the second question is "not very much" -- the number of situations in which you can prove that null is going to be dereferenced are very small. The answer to the third question has, over the last twelve years, always been "yes".

Therefore, no such feature.

Now, you might say, well, C# does have some limited ability to detect when an expression is always/never null; the nullable arithmetic analyzer uses this analysis to generate more optimal nullable arithmetic code (*), and clearly the flow analyzer uses it to determine reachability. So why not just use the already existing nullability and flow analyzer to detect when you've always dereferenced a null constant?

That would be cheap to implement, sure. But the corresponding user benefit is now tiny. How many times in real code do you initialize a constant to null, and then dereference it? It seems unlikely that anyone would actually do that.

Moreover: yes, it is always better to detect a bug at compile time instead of run time, because it is cheaper. But the bug here -- a guaranteed dereference of null -- will be caught the first time the code is tested, and subsequently fixed.

So basically the feature request here is to detect at compile time a very unlikely and obvioulsy wrong situation that will always be immediately caught and fixed the first time the code is run anyways. It is therefore not a very good candidate for spending budget on to implement it; we have lots of higher priorities.


(*) See the long series of articles on how the Roslyn compiler does so which begins at http://ericlippert.com/2012/12/20/nullable-micro-optimizations-part-one/

like image 153
Eric Lippert Avatar answered Nov 14 '22 23:11

Eric Lippert


While unreachable code is useless and does not affect your execution, code that throws an error is executed. So

if (true) { int hash = constObject.GetHashCode();}

is more or less the same as

throw new NullReferenceException();

You might very well want to throw that null reference. Whereas the unreachable code is just taking up space if it were to be compiled.

like image 45
Daniel Moses Avatar answered Nov 14 '22 22:11

Daniel Moses