Compile-time constants and reference types

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 correctly warns that int hash = o.GetHashCode(); not available.

In addition, constObject is a compile-time constant, and as such, the compiler correctly warns that int hash = o.GetHashCode(); unavailable since o != null will never be true .

So why the compiler did not understand that:

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

100% sure that this is an exception at runtime and thus produces a compile-time error? I know this is probably a stupid corner case, but the compiler seems like a pretty smart argument about the types of compile-time constant values, and I expected it to be able to figure out this little corner case with reference types as well.

+6
source share
4 answers

UPDATE: this question was t he was the subject of my blog on July 17, 201 . 2. Thanks for the great question!

Why does the compiler not find out that my code is 100% sure that it is an exception at runtime and thus produces a compile-time error?

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

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

into a compile-time error? But this is the exact opposite of what you want; you want this to be a runtime error to compile incomplete code.

Now you can say: well, dereferencing zeros is always undesirable always, while the exception "not implemented" is clearly desirable. So can the compiler detect only this particular situation when a null ref exception is thrown and give an error?

Of course available. We just need to spend a budget on implementing a data flow analyzer that tracks when a given expression is always null, and then makes it a compilation time error (or warning) to dereference this expression.

The questions to answer are as follows:

  • How much does this feature cost?
  • How much benefit does the user receive?
  • Is there any other opportunity that has a better cost-benefit ratio and provides the user with more benefits?

The answer to the first question is "pretty much" - road code flow analyzers for design and build. The answer to the second question is "not very many" - the number of situations in which you can prove that null will be dereferenced is very small. The answer to the third question over the past twelve years has always been yes.

Therefore, there is no such function.

Now you can say that C # has a limited ability to detect when an expression is always / never non-zero; a zero arithmetic analyzer uses this analysis to create a more optimal zero arithmetic code (*), and obviously, a flow analyzer uses it to determine reachability. So, why not just use an existing error and flow analyzer to detect when you always dereferenced the zero constant?

That would be cheap to implement, of course. But the corresponding user benefit is now tiny. How many times in real code do you initialize a constant to zero, and then cast it? It seems unlikely that anyone will really do this.

Moreover: yes, it is always better to detect an error at compile time instead of runtime, because it is cheaper. But the error here - guaranteed zero dereferencing - will be detected during the first testing of the code and subsequently fixed.

Thus, the main function request here is to detect a very improbable and accusatory incorrect situation during compilation, which will always be immediately detected and corrected the first time the code is run. Therefore, this is not a good candidate for spending the budget on its implementation; we have many priorities.


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

+11
source

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

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

more or less coincides with

 throw new NullReferenceException(); 

You might really want to throw away this null link. While unreachable code just takes up space if it needs to be compiled.

+4
source

It will also not warn about the following code, which has the same effect:

 throw new NullReferenceException(); 

There is a balance with warnings. Most compiler errors occur when the compiler cannot create anything meaningful.

Some happen to things that affect verifiability, or that cross the threshold of how likely they are to be a mistake. For example, the following code:

 private void DoNothing(out string str) { return; } private void UseNothing() { string s; DoNothing(s); } 

It will not compile, although if it were done, it would not hurt (the only place DoNothing does not use the passed string, so the fact that it is never assigned is not a problem). There's too much risk that I'm doing something stupid here to let it go.

Warnings are things that are almost certainly stupid, or at least not what you wanted. Dead code is likely to be an error to make the warning worthwhile, but probably reasonable enough (for example, trueOrFalse may change as the application develops) to make the error unacceptable.

Warnings are designed to be useful, not troubles, so the tablet is high enough for them. There is no exact science there, but it was considered that an unreachable code made a cut, and trying to throw out throwing exceptions was not the desired behavior.

There is no doubt that the compiler already detects unreachable code (and does not compile it), but sees one deliberate throw similar to the other, regardless of how confused it is, on the one hand, or directly on the other.

+2
source

Why do you even want this to be a compile-time error? Why do you need code to ensure that an exception is not valid at compile time? What if I, a programmer, want the semantics of my program to be:

 static void Main(string[] args) { throw new NullReferenceException(); } 

This is my program. The compiler has no business saying that this is invalid C # code.

+2
source

Source: https://habr.com/ru/post/906685/


All Articles