What does null! statement mean?
Asked Answered
A

7

255

I've recently seen the following code:

public class Person
{
    //line 1
    public string FirstName { get; }

    //line 2
    public string LastName { get; } = null!;

    //assign null is possible
    public string? MiddleName { get; } = null;

    public Person(string firstName, string lastName, string middleName)
    {
        FirstName = firstName;
        LastName = lastName;
        MiddleName = middleName;
    }

    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
        MiddleName = null;
    }
}

Basically, I try to dig into new c# 8 features. One of them is NullableReferenceTypes.
Actually, there're a lot of articles and information about it already. E.g. this article is quite good.
But I didn't find any information about this new statement null!
Can someone provide me an explanation for it?
Why do I need to use this?
And what is the difference between line1 and line2?

Alberic answered 16/2, 2019 at 14:53 Comment(6)
! is the null-forgiving operator, telling the compiler that, even though it normally wouldn't allow it, it should look the other way and allow it anyway, because we know better. null! itself has little practical use, as it all but negates the usefulness of nullable reference types. It's more useful when you know an expression can't be null, but the compiler doesn't.Thorianite
@JeroenMostert so smth like force? I mean even if it is unusual lets do that forced.Alberic
Yes, except it's more than unusual -- because string, under the new rules, is not a nullable reference type, and so should never be null. Assigning null! effectively says "I know this should never be null, but guess what, I'm doing it anyway". There's almost no program where that would make sense -- the only reason to do it would be because you know you're going to assign a non-null value before anyone could get a NullReferenceException, and want to signal that you haven't forgotten to assign it. Possible, but unlikely, so not very good as an example.Thorianite
@JeroenMostert I have found the null! to be useful in unit tests. Just because a reference shouldn't be null doesn't necessarily mean it won't be, so sometimes testing for the right behavior in that situation is appropriate.Floatation
@JeroenMostert It's also useful for game engines like Godot where you commonly defer reference field initialization for nodes and resources until the _Ready() callback method, instead of the constructor. It effectively shuts up the compiler.Adalai
From comments, Kotlin lateinit var equivalent.Infringe
V
446

TL;DR

The key to understanding what null! means is understanding the ! operator. You may have used it before as the "not" operator. However, since C# 8.0 and its new "nullable-reference-types" feature, the operator got a second meaning. It can be used on a type to control Nullability, it is then called the "Null Forgiving Operator".

Basically, null! applies the ! operator to the value null. This overrides the nullability of the value null to non-nullable, telling the compiler that null is a "non-null" type.


Typical usage

Assuming this definition:

class Person
{
    // Not every person has a middle name. We express "no middle name" as "null"
    public string? MiddleName;
}

The usage would be:

void LogPerson(Person person)
{
    Console.WriteLine(person.MiddleName.Length);  // WARNING: may be null
    Console.WriteLine(person.MiddleName!.Length); // No warning
}

This operator basically turns off the compiler null checks for this usage.

Technical Explanation

The groundwork that you will need to understand what null! means.

Null Safety

C# 8.0 tries to help you manage your null-values. Instead of allowing you to assign null to everything by default, they have flipped things around and now require you to explicitly mark everything you want to be able to hold a null value.

This is a super useful feature, it allows you to avoid NullReferenceExceptions by forcing you to make a decision and enforcing it.

How it works

There are 2 states a variable can be in - when talking about null-safety.

  • Nullable - Can be null.
  • Non-Nullable - Cannot be null.

Since C# 8.0 all reference types are non-nullable by default. Value types have been non-nullable since C# 2.0!

The "nullability" can be modified by 2 new (type-level) operators:

  • ! = from Nullable to Non-Nullable
  • ? = from Non-Nullable to Nullable

These operators are counterparts to one another. The Compiler uses the information that you define with these operators to ensure null-safety.

Examples

? Operator usage.

This operator tells the compiler that a variable can hold a null value. It is used when defining variables.

  • Nullable string? x;

    • x is a reference type - So by default non-nullable.
    • We apply the ? operator - which makes it nullable.
    • x = null Works fine.
  • Non-Nullable string y;

    • y is a reference type - So by default non-nullable.
    • y = null Generates a warning since you assign a null value to something that is not supposed to be null.

Nice to know: Using object? is basically just syntactic sugar for System.Nullable<object>

! Operator usage.

This operator tells the compiler that something that could be null, is safe to be accessed. You express the intent to "not care" about null safety in this instance. It is used when accessing variables.

string x;
string? y;
  • x = y
    • Illegal! Warning: "y" may be null
    • The left side of the assignment is non-nullable but the right side is nullable.
    • So it does not work, since it is semantically incorrect
  • x = y!
    • Legal!
    • y is a reference type with the ? type modifier applied so it is nullable if not proven otherwise.
    • We apply ! to y which overrides its nullability settings to make it non-nullable
    • The right and left side of the assignment are non-nullable. Which is semantically correct.

WARNING The ! operator only turns off the compiler-checks at a type-system level - At runtime, the value may still be null.

Use carefully!

You should try to avoid using the Null-Forgiving-Operator, usage may be the symptom of a design flaw in your system since it negates the effects of null-safety you get guaranteed by the compiler.

Reasoning

Using the ! operator will create very hard to find bugs. If you have a property that is marked non-nullable, you will assume you can use it safely. But at runtime, you suddenly run into a NullReferenceException and scratch your head. Since a value actually became null after bypassing the compiler-checks with !.

Why does this operator exist then?

There are valid use-cases (outlined in detail below) where usage is appropriate. However, in 99% of the cases, you are better off with an alternative solution. Please do not slap dozens of !'s in your code, just to silence the warnings.

  • In some (edge) cases, the compiler is not able to detect that a nullable value is actually non-nullable.
  • Easier legacy code-base migration.
  • In some cases, you just don't care if something becomes null.
  • When working with Unit-tests you may want to check the behavior of code when a null comes through.

Ok!? But what does null! mean?

It tells the compiler that null is not a nullable value. Sounds weird, doesn't it?

It is the same as y! from the example above. It only looks weird since you apply the operator to the null literal. But the concept is the same. In this case, the null literal is the same as any other expression/type/value/variable.

The null literal type is the only type that is nullable by default! But as we learned, the nullability of any type can be overridden with ! to non-nullable.

The type system does not care about the actual/runtime value of a variable. Only its compile-time type and in your example the variable you want to assign to LastName (null!) is non-nullable, which is valid as far as the type-system is concerned.

Consider this (invalid) piece of code.

object? null;
LastName = null!;
Vincentvincenta answered 16/2, 2019 at 15:18 Comment(28)
@canbax Nullabilty checks are only supported by c#8 and aboveVincentvincenta
@canbax a string could be null not any more. I mean if you use c# 8 and enable NullableReferenceTypes feature. VS immediately gives you a warning if you try assign null to string. But from the other hand you're able to introduce nullable string(string? s = null). No warning in that case.Alberic
"You should try to never use the ! Null-Forgiving-Operator. It negates the effects of null-safety you get guaranteed by the compiler." How are you going to write unit tests for argument validation? That's my most frequent use of ! in Noda Time.Barth
@JonSkeet I'd consider unit-tests a special case here - Since you intentionally work with nulls. With "You should try to never use" I was referring to "normal" code - Where using this operator can yield unexpecting results. Aka something became null even though you declared it non-nullable. I wanted to tell people not to slap a ! everywhere - just to get rid of the warning. Because then you dont get the benefits of null-safety - Will maybe edit later to make more clearVincentvincenta
I would definitely recommend editing later - currently anyone who is widely using the null-forgiving operator in their tests could easily feel bad about it given your current text, which doesn't give any indication of limitations to the advice. I think "never" is likely to be unachievable in several code bases - for example, in Noda Time we have an invariant in parse results that means I never expose a null value, but I still end up with one where the parse itself has failed. I think "try hard to avoid" has a more positive tone. Just my opinion though.Barth
@JonSkeet Yeah, you are right - One should definitely not feel bad about using this operator in a test context. I replaced "never" with "try to avoid" and added unit tests to the "exception" list. You really make me overthink my whole post.Vincentvincenta
Still not 100% sure from this answer why I'd use string x = null!;. My quick guess is b/c you know you're going to overwrite that null value in the same code where the var was initialized. But then why initialize at all? Does a non-nullable string initialize to string.Empty by default and that's bad? (I can't think of a use case in this situation (you are going to write a non-null value "soon") where it would be.) But if there's a use case; why not use a nullable string if checking against null is your goal? Skeet's test case -- null where there shouldn't be -- is the only that make sense.Dixie
@Dixie There is very little reason to use null! the example given in the question also does not demonstrate a real use-case imo. There are a few situations where you would want to use <something>! tho (See the "Why does this operation exist then" Section). This post is more about giving you the tools to understand this syntax/feature. This is the kind of feature where you "just know" you need it when encountering something specific while coding... A third of the post is also telling you to use this feature very carefully since like you said - using this may be very "out of the ordinary"Vincentvincenta
@Dixie string x = null!; as a variable is not used very often that I'm aware of, but as a field it is important in cases where you want to treat the field as not-nullable, but can't initialize it right away even though you know it will/should be initialized before you start to make use of it. (another option is to make it nullable anyway and just always null-check if before every usage, which can also be a valid approach.) (and another option can be to use properties or methods like GetXOrThrow or XOrDefault to make the null-checking more straightforward.)Dearly
@DaveCousineau But see above: "But then why initialize at all? Does a non-nullable string initialize to string.Empty by default and that's bad?" I mean, it's got some value when it's declared; what is that and why is !null better if you're immediately assigning? As Patrick clarified, there doesn't appear to be much practical use for !null at all -- except in cases, like Skeet's, where you're testing horn of a rabbit situations.Dixie
@Dixie as a field, string x; will default to null and you will get a warning saying that the non-nullable field will have a null default value. string x = null!; removes this warning. (! does two things: changes a nullable type to a non-nullable type; and also suppresses nullability warnings in general for that expression (not 100% sure of the range-of-effect of the suppression).)Dearly
@Dixie It is worth noting that null! is not what the null-forgiving operator was primarily designed for. It is merely an "artifact", something that exists for completeness. It is mainly intended to be used on nullable variables. Someone that knows the nullable-reference types feature will most likely be able to figure out what null! means rather quickly, This question was posted when the feature was still new, therefore my answer became a sort of introduction to the feature. But I find many people find it and think null! is something that is used alot, when in reality it's not.Vincentvincenta
@PatrickHollweck it depends what you're doing. I use null! all the time, and almost never use it in other kinds of expressions. null! is extremely useful (or even completely necessary) when initializing variables/properties implicitly.Dearly
You have mentioned that x is a reference type - So by default non-nullable. - reference type by default have null value - isnt it?Exuberant
Also, suppose if I initialize a property with public string Prop1 { get; set; } = null!; in order to get rid of the warning, then in my code when I initialize this class and set this instance1.Prop1 = null;, it doesn't give me any warning. Does =null! at property level hide that warning also?Exuberant
@Exuberant Yeah a reference type will default to null if not assigned. But the compiler enforces the assignment of non-nullable variables. I cannot reproduce what you have stated in your second comment. I created this repl to demonstrate the behaviour: replit.com/@PatrickHollweck/so-null-assign#main.cs Take a look if you want :)Vincentvincenta
@PatrickHollweck What would then be the general consensus for using = null! when defining properties on an object? Those properties should never be assigned null, but I don't want to have to declare them all as nullable just to get rid of warnings? Or is that the way to go....but then I would do a nastier thing of using ! when referencing those properties.Fontainebleau
@PatrickHollweck Are you sure this is correct?: "Nice to know: Using string? is syntactic sugar for System.Nullable<string>"? When I do ´string? s = null;´ the IL-Code will be IL_0000: ldnull IL_0001: stloc.0 // sDiaphysis
@Fontainebleau I don't understand your Question, can you elaborate? You have an object with optional properties that should also be not null, and you don't know how to initialize them?Vincentvincenta
@Diaphysis I know very little about .NET IL so I don't understand what you are trying to tell me. But from what I researched "ldnull" initializes a property with null which is exactly what you are doing in your code sample. I am fairly sure that null-checking does change the emitted IL much or at all. System.Nullable is also an opaque type which you cannot interact with directly when generated by the compiler, since it does automatic boxing/unboxingVincentvincenta
@PatrickHollweck string is a nullable (reference) type. string? is in the "Null safty" environment only a information for static code analysis that it accept null without warning. the Nullable-Struct accepts for the generic type parameter only valuetypes. When you use int? this will be converted to System.Nullable<int> because int is a valuetype and valuetype do not accept null.Diaphysis
@PatrickHollweck When you define a POCO with a property you can have do it into ways class POCO { public string Name { get; set; } } vs class POCO { public string? Name { get; set; } }. Both of these have semantically different meanings. Now I don't want to use the latter when I know that Name won't actually be null when it gets used. But using the the initial causes a spamming of warnings. Which you can suppress using the null suppressing statement i.e. the initial becomes class POCO { public string Name {get; set; } = null! }. So I was referring to ` = null!` in that context.Fontainebleau
@Fontainebleau OK so you have an object with a property that semantically should never be null so you don't mark it nullable. But you get a warning from the compiler because you don't initialize the property. Which you have to do, because if you don't the value ends up as null. I think we have a A/B Problem here. The answer is: Give the class a constructor and don't create the object until you have a value to initialize the property with. Otherwise it does not make sense to make the property non-nullable. You can't have both. = null! Should not be used for this...Vincentvincenta
This explanation is peace of art. much more better than the Microsoft said. So many thanks @PatrickHollweckCoinstantaneous
Here: Nice to know: Using object? is basically just syntactic sugar for System.Nullable<object>, what do you mean by "syntactic sugar"? Can you explain any further? May be that sentence explanation is not that sweet, if you know what I mean.Clavicle
@Clavicle Nothing a quick google search cannot explainVincentvincenta
I know that, but Google does not provide a contextual explanation from inside this answer. Googling the whole sentence is just a copy + paste move. I can't put together nor relate that sentence inside the context of the section "? Operator usage" (and maybe the whole answer).Clavicle
@Clavicle Someone who knows what "syntax sugar" is, can reasonably be expected to understand that sentence. I cannot explain the whole of programming in this answer, it is long enough already. If you are genuinely confused: Anytime you write TYPE? the compiler turns it into System.Nullable<TYPE>Vincentvincenta
B
27

null! is used to assign null to non-nullable variables, which is a way of promising that the variable won't be null when it is actually used.

I'd use null! in a Visual Studio extension, where properties are initialized by MEF via reflection:

[Import] // Set by MEF
VSImports vs = null!;
[Import] // Set by MEF
IClassificationTypeRegistryService classificationRegistry = null!; 

(I hate how variables magically get values in this system, but it is what it is.)

I also use it in unit tests to mark variables initialized by a setup method:

public class MyUnitTests
{
    IDatabaseRepository _repo = null!;

    [OneTimeSetUp]
    public void PrepareTestDatabase()
    {
        ...
        _repo = ...
        ...
    }
}

If you don't use null! in such cases, you'll have to use an exclamation mark every single time you read the variable, which would be a hassle without benefit.

Note: cases where null! is a good idea are fairly rare. I treat it as somewhat of a last resort.

Bobble answered 11/6, 2021 at 21:8 Comment(6)
This is the only answer that actually explains why on Earth one might actually want to write null! in their source code.Edger
No, it does not, In both of these cases, you are better of not assigning the variable at all. There is no reason to default initialize those members with nullVincentvincenta
@PatrickHollweck No, failure to assign a value causes warning CS8618 (or in struct constructors, error CS0843.)Bobble
No offense, but this is circular logic. Agree with Patrick. This also appears to expose a massive flaw in c# 8 reasoning, now. By defaulting ref types to all non-nullable, Microsoft has stripped out an important state of initialized objects, which was null. If we are forced to use this new logic now, you are better off creating a default value for every type than the old way using these phony ignored nulls. Otherwise, this looks silly...Liston
There is no circle in the logic.Bobble
A much better explanation than the accepted wall of text that doesn't explain what you mentioned in your very first sentence - it's a promise that the value won't be null. That's what confused me in my own code - I have a non-nullable property that I initialize in a method that I call in the constructor but the compiler complains that I'm not initializing it. Rider conveniently suggested to use null! and now it makes sense why it would suggest that.Inofficious
G
23

When the "nullable reference types" feature is turned on, the compiler tracks which values in your code it thinks may be null or not. There are times where the compiler could have insufficient knowledge.

For example, you may be using a delayed initialization pattern, where the constructor doesn't initialize all the fields with actual (non-null) values, but you always call an initialization method which guarantees the fields are non-null. In such case, you face a trade-off:

  • if you mark the field as nullable, the compiler is happy, but you have to un-necessarily check for null when you use the field,
  • if you leave the field as non-nullable, the compiler will complain that it is not initialized by the constructors (you can suppress that with null!), then the field can be used without null check.

Note that by using the ! suppression operator, you are taking on some risk. Imagine that you are not actually initializing all the fields as consistently as you thought. Then the use of null! to initialize a field covers up the fact that a null is slipping in. Some unsuspecting code can receive a null and therefore fail.

More generally, you may have some domain knowledge: "if I checked a certain method, then I know that some value isn't null":

if (CheckEverythingIsReady())
{
   // you know that `field` is non-null, but the compiler doesn't. The suppression can help
   UseNonNullValueFromField(this.field!);
}

Again, you must be confident of your code's invariant to do this ("I know better").

Gibbosity answered 16/2, 2019 at 18:8 Comment(0)
F
4

I don't believe this question can be discussed without specific C# version being used.

public string RpyDescription { get; set; } = null!;

This is common in .NET 6 Core ... in fact it's required if you want to describe a string as not being nullable. One reason this exists is when one is working with SQL databases where a field can be set to "Allow Null". Even more so when working with JSON structures that accept null. EF needs to know.

Reference Type (Heap - pointer to memory location where the data is stored) of Value Type (Stack - memory location where data is stored).

.NET 6 (C#10) enables nullable context for the project templates by default (prior to this nullable context is disabled by default).

In EF/Core, it's very important to understand relationship between database null and model/entities null.

Filamentous answered 26/5, 2022 at 18:23 Comment(4)
you are right, I would actually go further, whatever API, Database, GUI, JSON structure. You need to lay out for other programmers the "Null Rules", for example what type of wheels does a car have. If we allowed this to be null when our Database needs this to be a not null value, or the JSON we are creating needs to be a not null value. This would be far inferior to what we have now, where we can specifically state the null rules. edge cases, should be dealt with by creating some reasonable default value and making a comment about why this object is being instantiated without that value.Palmapalmaceous
Why not public string RpyDescription { get; set; } = "";?Bobble
@Bobble basically (when using EF) "string? Property" signifies an optional property, "string Property = string.Empty;" is a mandatory property with a default value of string.Empty and "string Property = null!;" is a mandatory property without a default value, i.e. the property must be explicitly set before the entity can be written to the database.Verdie
@Bobble In my job (banking) assign empty string as default value is very super extremely very dangerous. Did I say very twice?Grower
M
4

One valid use case is when the values of an object are assigned at runtime, such as with IOption property mappings which are required.

You can either:

  1. Mark the properties as nullable and deal with null checks everywhere.
  2. Set a default value of "" which will allow the configuration property to be undefined, and then check for string.IsNullOrEmpty() whenever it's consumed.
  3. Set the default value as null!, which disables the IDE warning, and will force the app to exit on configuration startup if the property isn't set.
Metamorphism answered 7/8, 2023 at 8:27 Comment(0)
L
2

Since C# 11, you no longer need to use null!. Moreover, there are almost no cases when you need null-forgiving ! at all.

C# introduced a required modifier, which is super handy.

For example, when you don't even need a constructor.

public class Person
{
    public required string FirstName { get; init; }

    public required string LastName { get; init; }

    public string? MiddleName { get; init; }
}

In such a case, the compiler will not allow you to create an instance without specifying all required fields.

Example:

var p = new Person {
  FirstName = "Xyz"
}

It will not compile because LastName is missing.

Bonus: System.Text.Json is aware of the required keyword.

For example, if you define a controller with [FromBody] Person person and somebody calls your API without the required field, it will throw an error immediately.

All your classes get validated before getting into your logic. As a result, if the property is marked as it can't be null, it will NEVER be null.

We introduced this approach to our project, and we no longer get NullPointerException anymore.

PS: Just specify <WarningsAsErrors>Nullable</WarningsAsErrors> in your .csproj and enjoy your code statically validated for null issues in compile time.

Lindie answered 2/2 at 18:58 Comment(0)
P
1

This question needs to be updated, in C# 10 in object relational mapping, this operator, combined with the ? operator is critical, this is a way to tell other coders on the project, future coders, and remind yourself how the data is going to end up being consumed and the null rules regarding that data.

public string Data { get; set; } = null!;
public string? Nullable { get; set; }

The beauty of this is that you don't have to go and look at the API docs(which you might not have), or go look through your database (which you might not even have access to). You already know by glancing at the class what the rules regarding null values are.

The downside is that if you instantiate the class, and don't have the information you need to instantiate the NOT NULL values, you will get strange default values that don't always make sense. First of all this doesn't happen nearly as commonly as people think and often comes from lazy programming. When you instantiate an instance of a class. You should calculating or assigning those NOT NULL properties. Right away. If you declare a car, and in your database or API cars have wheels, if you declare a car without assigning property values to the wheel.

Then did you truly instantiate the car in a meaningful way? You certainly didn't define the car the way the Database understands a car, it's a car with no wheels and it shouldn't be, doing this might be convenient but it goes against basic principles of object oriented programming.

are there exceptions for example perhaps the value can't be known at that point in time or until other events have transpired in these edge cases. Create a meaningful default value manually, if your default value hits the database you will know EXACTLY what's wrong

for people discussing unit tests why would you test the null case when the null case is impossible in the context of the object there is no need to have an understanding of how a car without wheels would behave for example. This is a silly, badly designed test.

I would go so far as to say that when it comes to strings this operator should be used the overwhelming majority of the time. When we declare int, or bool, or float, we have the understanding that these cannot be null unless we say so explicitly. Why in the world would you advocate for a different rule for strings!? the fact that we couldn't do this with strings previously was a design flaw.

Palmapalmaceous answered 24/10, 2022 at 22:51 Comment(1)
I'm also going to add that very often there is the assumption that the "Code First" model is being used. In real life, the data and the rules surrounding it, almost always exists before the application. The number of applications written Database First is severely underestimated. Writing Code First is great but very often it's impractical. You don't always have that kind of control over your database.Palmapalmaceous

© 2022 - 2024 — McMap. All rights reserved.