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
- Nullability in C# - What It Is and What It Is Not
- Null Conditional Operators in C# - ?. and ?[]
- Null Forgiving Operator in C# - ! (this article)
- Null Coalescing Operators in C# - ?? and ??=
- C# "var" with a Reference Type is Always Nullable
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.
Next Article: Null Coalescing Operators in C# - ?? and ??=
Happy Coding!
No comments:
Post a Comment