isxaker
isxaker

Reputation: 9456

What does null! statement mean?

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?

Upvotes: 287

Views: 115704

Answers (8)

Michael Tsang
Michael Tsang

Reputation: 781

In short:

It is an oxymoron, something that is self-contradictory. It is never a good idea to that.

Full explanation:

When null-safety compile-time feature is on in C#, reference types can't be assigned null. The postfix ! operator is used to silence the compiler that "I know the expression is not null. I know what I am doing."

For example,

string s = SomeApiCall()!;

That API call sometimes can return null, but if you know in this particular situation it won't return null, you can silence the compiler to allow it to compile.

However, the ! operator has no runtime effect. If the API call indeed returns null, the code will still run, and if s is dereferenced afterwards, a NullReferenceException will occur.

So when you assign null! into a variable, you are placing a time bomb into your code as it will explode when the variable is dereferenced, unless it has been overwritten with a valid value. It has no meaningful practical use and if you ever find yourself writing such oxymoron you should redesign your classes.

Upvotes: 1

Seagull
Seagull

Reputation: 3600

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.

Upvotes: 3

Kristian Williams
Kristian Williams

Reputation: 2343

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.

Upvotes: 5

Patrick Hollweck
Patrick Hollweck

Reputation: 6665

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

Upvotes: 483

Qwertie
Qwertie

Reputation: 17186

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.

Upvotes: 35

jdmneon
jdmneon

Reputation: 474

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.

Upvotes: 2

Rob
Rob

Reputation: 61

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.

Upvotes: 4

Julien Couvreur
Julien Couvreur

Reputation: 4963

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

Upvotes: 23

Related Questions