What does null! statement mean?

18,982

Solution 1

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"


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

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 - Can not 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, you define with those operators, to ensure null-safety.

Examples

? Operator usage.

This operator tells the compiler that a variable can hold a null value.

  • 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 string? is syntactic sugar for System.Nullable<string>

! 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.

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!;

Solution 2

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").

Solution 3

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.

Share:
18,982

Related videos on Youtube

isxaker
Author by

isxaker

Updated on December 12, 2021

Comments

  • isxaker
    isxaker over 2 years

    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?

    • Jeroen Mostert
      Jeroen Mostert over 5 years
      ! 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.
    • isxaker
      isxaker over 5 years
      @JeroenMostert so smth like force? I mean even if it is unusual lets do that forced.
    • Jeroen Mostert
      Jeroen Mostert over 5 years
      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.
    • Jesse
      Jesse over 4 years
      @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.
    • knightofiam
      knightofiam over 3 years
      @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.
  • canbax
    canbax over 5 years
    a string could be null. You can assign null value like this string str = null; I'm using c# 6 .net 4.6.1. I think your statement is wrong "This line defines a non-nullable class property named LastNameof type string. Since it is non-nullable you can technically not assign null to it - obviously."
  • Patrick Hollweck
    Patrick Hollweck over 5 years
    @canbax Nullabilty checks are only supported by c#8 and above
  • isxaker
    isxaker over 5 years
    @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.
  • Jon Skeet
    Jon Skeet over 5 years
    "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.
  • Patrick Hollweck
    Patrick Hollweck over 5 years
    @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 clear
  • Jon Skeet
    Jon Skeet over 5 years
    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.
  • Patrick Hollweck
    Patrick Hollweck over 5 years
    @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.
  • ruffin
    ruffin about 3 years
    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.
  • Patrick Hollweck
    Patrick Hollweck about 3 years
    @ruffin 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"
  • Dave Cousineau
    Dave Cousineau about 3 years
    @ruffin 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.)
  • ruffin
    ruffin about 3 years
    @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.
  • Dave Cousineau
    Dave Cousineau about 3 years
    @ruffin 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).)
  • Patrick Hollweck
    Patrick Hollweck about 3 years
    @ruffin 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.
  • Dave Cousineau
    Dave Cousineau about 3 years
    @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.
  • user3840170
    user3840170 about 3 years
    This is the only answer that actually explains why on Earth one might actually want to write null! in their source code.
  • Patrick Hollweck
    Patrick Hollweck about 3 years
    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 null
  • Qwertie
    Qwertie about 3 years
    @PatrickHollweck No, failure to assign a value causes warning CS8618 (or in struct constructors, error CS0843.)
  • Stokely
    Stokely almost 3 years
    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...
  • Qwertie
    Qwertie almost 3 years
    There is no circle in the logic.
  • variable
    variable over 2 years
    You have mentioned that x is a reference type - So by default non-nullable. - reference type by default have null value - isnt it?
  • variable
    variable over 2 years
    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?
  • Patrick Hollweck
    Patrick Hollweck over 2 years
    @variable 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 :)
  • Heinrich
    Heinrich about 2 years
    @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.
  • BennoDual
    BennoDual about 2 years
    @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 // s
  • Patrick Hollweck
    Patrick Hollweck about 2 years
    @Heinrich 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?
  • Patrick Hollweck
    Patrick Hollweck about 2 years
    @BennoDual 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/unboxing
  • BennoDual
    BennoDual about 2 years
    @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.
  • Heinrich
    Heinrich about 2 years
    @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.
  • Patrick Hollweck
    Patrick Hollweck about 2 years
    @Heinrich 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...