Tuesday, July 19, 2022

Null Forgiving Operator in C# - !

Over the last couple articles, we have been looking at nullability in C#. When we have nullability enabled, we need to respond to warnings that come up in our code. Sometimes these warnings are incorrect (after all, the analyzers are not infallible). Fortunately for us, there is a null forgiving operator (represented by an exclamation point) that we can use to say that we want to ignore one of these incorrect warnings.

Articles

The source code for this article series can be found on GitHub: https://github.com/jeremybytes/nullability-in-csharp.

The Short Version

Sometimes the warnings that we get around nullability are incorrect. When we are confident that an object will not be null, we can use the null forgiving operator (also called the null suppressing operator) to remove the warning. We do this by using an exclamation point.

In the following example, the analyzer thinks that the "Exception" property on task may be null. We can tell the analyzer that we know it is not null by using the null forgiving operator.


    task.Exception!.Flatten().InnerExceptions
If we add the "!" after the "Exception" property, the warning is supressed.

I tend to think of this as the I-know-what-I'm-doing operator. Let's take a closer look at the code to better understand this.

Incorrect Warning

For the most part, the warnings that we get when nullability is enabled are pretty accurate. But sometimes, there is not enough information, and a warning is incorrect. We'll use the same sample code from the prior articles: https://github.com/jeremybytes/nullability-in-csharp.

As a reminder, the "StartingCode" folder has the starting state of the code at the beginning of the first article in the series. The "FinishedCode" has the completed code. The links in this article will point to the "FinishedCode" folder unless otherwise noted.

Here's an example from inside the "FetchWithTaskButton_Click" method in the "MainWindow.xaml.cs" file of the "UsingTask.UI" project: 


    if (task.IsFaulted)
    {
        foreach (var ex in task.Exception.Flatten().InnerExceptions)
            Log(ex);
    }

The "Exception" property on the "task" variable is nullable. So we get a warning:


    'Exception' may be null here.
    CS8602: Dereference of a possibly null reference.

This is the same warning that we have seen in previous articles. But in this case, the warning is incorrect.

Task.IsFaulted
The reason this is incorrect is because of the "if" statement that checks the "IsFaulted" property on "task".

When a task is faulted, it means that an exception was thrown during the task execution. If the "IsFaulted" property is true, then the "Exception" property will be populated (meaning, it is not null).

So even though the "Exception" property is nullable, we know that it will not be null in this block of code.

One way of fixing this is to add a "#pragma" to the code to suppress the warning. Personally, I really don't like this approach because it clutters up the code. (If you are curious, you can use "Ctrl+." to have Visual Studio add the "#pragma" for you.)

Fortunately, there is another solution: use the null forgiving operator.

Null Forgiving Operator - !

With the null conditional operator (from the last article), we add a question mark after the object that may be null. The null forgiving operator uses an exclamation point.

Here is the updated code:


    if (task.IsFaulted)
    {
        foreach (var ex in task.Exception!.Flatten().InnerExceptions)
            Log(ex);
    }

With the operator added, the warning is suppressed. This is a way of saying "I know what I'm doing. This object is not going to be null here".

Another Example

As another example, let's look at the return value of a method, specifically the "GetAsync" method in the "PersonReader.cs" file of the "UsingTask.UI" project. (Note: this snippet is from the "StartingCode" folder.)


    public async Task<List<Person>> GetAsync(
        CancellationToken cancelToken = new CancellationToken())
    {
        [collapsed code]

        var result = JsonSerializer.Deserialize<List<Person>>(stringResult, options);
        return result;
    }
    'result' may be null here.
    CS8603: Possible null reference return.

The "GetAsync" method returns a "Task<List<Person>>". The "List<Person>" part is non-nullable.

The "Deserialize" method on the "JsonDeserializer" may return null (depending on the string that is being parsed).

So in the code sample, the "result" variable may be null. Because of this, we get a warning that we have a "possible null reference return".

In this case, I'm an not particularly concerned about the "Deserialize" method returning null. This is a known service (that I control), and the method has checks in place to make sure the service call is successful.

Ignoring the Warning
We can use the null forgiving operator to ignore the warning by putting the exclamation mark after the "result".


    public async Task<List<Person>> GetAsync(
        CancellationToken cancelToken = new CancellationToken())
    {
        [collapsed code]

        var result = JsonSerializer.Deserialize<List<Person>>(stringResult, options);
        return result!;
    }

This suppresses the null warning. But this may not be the best option for this code.

Null Coalescing Operator - ??
Another option is to use the null coalescing operator:


    public async Task<List<Person>> GetAsync(
        CancellationToken cancelToken = new CancellationToken())
    {
        [collapsed code]

        var result = JsonSerializer.Deserialize<List<Person>>(stringResult, options);
        return result ?? new List<Person>();
    }

Instead of suppressing the warning, the null coalescing operator says "if 'result' is null, then return a new empty list."

We will take a look at the null coalescing operator in the next article.

Wrap Up

The null analyzers are not always accurate. Sometimes we get null warnings on objects that we know will not be null. In these cases, we can use the null forgiving operator to suppress the warnings.

Enabling nullability gives us warnings to help us eliminate possible nulls in our code. And the null operators (including the null forgiving operator) can help us in dealing with those warnings.

In the next article, we will look at the null coalescing operators. These give us additional tools when dealing with possible nulls in our code. Be sure to check back details.


Happy Coding!

No comments:

Post a Comment