The C# compiler is pretty smart. For example, let's say you have a function that returns either a string or null.
string? GetString()
{
...
}
var s = GetString()
Then if you try to naively use a string function, it will warn you. Eg. s.Contains(...)
will generate warning CS8602: Dereference of a possibly null reference.
. You could solve this with with the null conditional operator ?
: s?.Contains(...)
. Alternatively, you could use an if
statement to check if it is not null before using it.
if (s is not null)
{
s.Contains("Hello");
}
Now the compiler is smart enough to know that if that check was true, s
must really be a string, and it will no longer give the warning.
However, what if you had your own validation function? For example:
bool CheckString(string? s)
{
return s is not null && s.Contains("World");
}
if (CheckString(s))
{
s.Contains("Hello");
}
Now the compiler again complains with the same error, despite the function checking the string is not null. This is one of the few cases where we are smarter than the compiler!
One way to solve this, is to use the null forgiving operator !
: s!.Contains("Hello");
. Here we assert to the compiler that we are sure s
will not be null in this case.
However, you would have to use this everywhere that the validation function is used. The null forgiving operator should only be used in rare cases to prevent it becoming a bad habit, as it can be misused.
Instead, we can tell the compiler that anytime this function returns true, the input value must be non-null. We can do this with the NotNullWhen
attribute.
bool CheckString([NotNullWhen(true)] string? s)
{
return s is not null && s.Contains("World");
}
Now the compiler doesn't complain!
A real example of this is the String.IsNullOrEmpty
method:
public static bool IsNullOrEmpty([NotNullWhen(false)] string? value)
{
return value == null || value.Length == 0;
}
You can find several similar attributes here.