18

I am confused. I thought enabling c# 8 and nullable reference types will prevent the assignment of null to a non nullable reference types, but apparently it is only a warning at compile time, I know you can enforce it to be an error and stop it build, but I thought it is more than just a compiler check.

Look at this example https://dotnetfiddle.net/4eCs5L

If you run, it is still possible to assign null to non nullable reference type, why isn't that throwing an error at run time?

Mocas
  • 1,403
  • 13
  • 20
  • 4
    Unfortunately, this is wrong. There are no runtime checks added by this compiler flag. There is metadata added, that the compiler can use to give warnings, but the runtime behavior of the code does not in any way change because of this flag. It only changes if you explicitly start fixing the warnings/errors by adding the appropriate checks and workarounds yourself. – Lasse V. Karlsen May 06 '20 at 11:32
  • 2
    So the non-nullable reference type is not "watched/checked" in run time for value type assignmen? Why? – Mocas May 06 '20 at 11:57
  • Because they designed it not to be. Let me rephrase that. They did not design this feature to be a runtime check, and thus it does not do runtime checking. You/we might want a different implementation, but this is what they gave us. It behaves exactly like they designed it to behave. – Lasse V. Karlsen May 06 '20 at 14:06
  • And it would be nearly impossible to close that hole 100% anyway, see https://dotnetfiddle.net/8LTF0T - there would be lots of things that would need an overhaul to detect this kind of situation if you were to outright ban the presence of null references in these variables. Note that the code in the fiddle doesn't use the nullability things, but it shows that even if you declare a field to not have a null value, briefly it does during initialization, and depending on order of initialization, you might observe this value before the variable gets assigned something non-null. – Lasse V. Karlsen May 06 '20 at 14:14
  • Thank you @Lasse – Mocas May 06 '20 at 23:52
  • Hi @LasseV.Karlsen, can you please put your comment in an answer, I would like to resolve this question – Mocas Jul 03 '20 at 07:21

4 Answers4

11

TLDR: Backwards compatibility

If Nullable Reference Types would have been part of C# 1, then a null assignment to a non-nullable type would emit a compile error.

The problem of C# is there is already a lot of existing code without Nullable Reference Types. Compiler errors on null assignment would break all that existing code or libraries.

You can find the full explanation in the .NET Blog Post from the C# Program Manager: https://devblogs.microsoft.com/dotnet/nullable-reference-types-in-csharp/

Christian Held
  • 2,321
  • 1
  • 15
  • 26
  • 5
    It is still an option to enable it. If it break all existing code, then don't enable it. – Mocas May 11 '20 at 23:35
6

The purpose of the feature is to give developers more tools to catch the most prevalent type of errors there is, derefencing a null reference / pointer and thus crashing the application.

As such, the C# design team added "nullable reference types", with enough syntax that the developer can more clearly declare the intent and requirements of code.

Things that can be expressed by the new syntax:

  • Properties and fields that either allow NULL or does not allow NULL
  • Method parameters that can be NULL, or should be NULL
  • Method return values that can be NULL, or won't be NULL

When enabled, the compiler will use this new syntax, and attributes and other metadata, to start giving warnings when it sees code that isn't "guaranteed" to be correct. In other words, if you read a property that says it can return NULL, and try to just pass that in as a parameter value where the method says that the parameter shouldn't be NULL, you will get a warning.

You can, using existing directives on projects, say that certain warnings should instead be handled as errors, and break the build, and this includes these new warnings.

HOWEVER, what they didn't add was runtime checks. If you say that a parameter value should never be NULL, and either ignore the warning or bypass it (there are ways to say "trust me, this is correct"), then the method will run as stated. There are no invisible if-statements or guard statements that verify that the parameter is not null.

Basically, you should still add such if-statements.

This is, contrary to your belief, exactly what they designed the feature to do. They did not forget to add these runtime checks, they purposefully didn't add them.

For instance, if you call a method through reflection, the compiler is not involved since it is a runtime thing, and thus no checks will be performed at all. If the method doesn't have such guard statements, likely it will crash later with a NullReferenceException.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • `Basically, you should still add such if-statements.` I've been recently debating whether I'm being overly pedantic by adding these checks. I thought that the `object param!!` preview feature was going to save me a lot of hassle, but they kicked it back out again. This feels rather like retro-fitting rocket-proof windows into your office building. They're never going to work as well after the fact, but here we've already got the building. *shrugs* – monkey0506 Apr 23 '22 at 03:52
  • The best explanation I find so far for this confusing question. – Chris Bao Aug 02 '23 at 12:52
2

Using this reference https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references

I can see that the directive #nullable enable: Sets the nullable annotation context and nullable warning context to enabled

If you are still expecting it to throw error, add this to your project file <WarningsAsErrors>nullable</WarningsAsErrors>

Compiler warnings for null assignments

Rikki Gibson
  • 4,136
  • 23
  • 34
CodeReaper
  • 775
  • 2
  • 6
  • 21
  • My understanding is that you can't set a non-nullable reference type to null. That's why you get the warning. But when you run the code, you see it is ok to do that. – Mocas May 06 '20 at 11:48
  • 1
    At this point if you want to enforce the behaviour, there is one option to add this in your csproj `CS8600;CS8602;CS8603;CS8616;CS8618;CS8625` – CodeReaper May 06 '20 at 14:00
  • And this will still only make the compiler produce errors if you write incorrect code. You can, still, however, write code that *lies* to the compiler, or uses the null-forgiving operator, you can still end up with a null in a variable you didn't intend to be null. In other words, this is more tooling on the code-writing stage, but it does not replace runtime null-checks. – Lasse V. Karlsen May 06 '20 at 14:09
  • @CodeReaper, as per my question, I know that you could do that, but that only applies the check at compile time, my question was about why the check is not done at run time as well. As per Lasse comment, that is as designed. – Mocas May 06 '20 at 23:51
  • Yes, that is correct and I have also provided the reference above that explains exactly this behaviour. It does not say anywhere that it will throw an error. Only that static code analysis will give warnings. After reading that reference it should clarify how its designed to work. – CodeReaper May 07 '20 at 08:59
  • Sorry, but your answer doesn't really answer my question, and I already mentioned that I know about your suggestion on how to make it error (still it won't throw an exception). I would have accepted the comments from Lasse as an answer if he had put them as one. But it is fair to say that the link you provided is useful. – Mocas May 08 '20 at 04:29
0

I would like to just point out if you inject <WarningsAsErrors>Nullable</WarningsAsErrors> by Directory.Build.props, it is invalid approach.

You should use Directory.Build.targets, see this question

(Note: I've landed on this question for the second time when I was solving the same problem related indirectly to the question, but solution for my exact problem was. ⬆️ )

KUTlime
  • 5,889
  • 1
  • 18
  • 29