Search code examples
c#.net-corec#-10.0

Solve "Nullable value type may be null" on values1.Count in NetCore 6


Using NetCore 6 and <Nullable>enable</Nullable> on project definition I have:

protected Boolean CrossBelow<T>(IList<Nullable<T>> values1, IList<Nullable<T>> values2) where T : struct, IComparable {

  if (values1[values1.Count - 2] == null || values1.Last() == null || values2[values1.Count - 2] == null || values2.Last() == null)
    return false;

  return values1[values1.Count - 2].Value.CompareTo(values2[values2.Count - 2].Value) > 0 && values1.Last().Value.CompareTo(values2.Last().Value) < 0;

} 

But I am getting the Warning:

Nullable value type may be null

On a few places like:

values1[values1.Count - 2]

I am able to solve part of it using:

values1?[values1.Count - 2]

But I get the compilation error

`cannot convert from 'int?' to 'int'` 

When trying the same on values1.Count:

values1?[values1?.Count - 2]

How to solve this?


Solution

  • The problem is that you are directly accessing the Value property of Nullable<T>. The compiler doesn't know if the values in your list at the last and second to last index might change between your null checks and when you're accessing these elements in the return statement.

    The simplest solution here is to use the null-conditional operator ?.. This would allow you to even remove the explicit null checks as now everything happens in one go:

    protected bool CrossBelow<T>(IList<T?> values1, IList<T?> values2) where T : struct, IComparable =>
           values1[^2]?.CompareTo(values2[^2]) > 0
        && values1[^1]?.CompareTo(values2[^1]) < 0;
    

    I also simplified your array accesses using indices.

    So what's happening in above code?

    Your method takes two ILists values1 and values2 which contain values of T? (which is the same as Nullable<T> only shorter). Note that the IList instances themselves can not be null (or at least we don't expect them to be). Therefore we don't need any null-conditionals after the lists themselves. So the ? after values1 in values1?[values1.Count - 2] is not needed. The reasons you were getting warnings is because the element returned by the index operation may be null. Therefore we apply the ? operator after we indexed the element we want.

    Thanks to the ?. operator execution now only continues to the CompareTo() statement if the value returned by the previous indexing operation is not null. As CompareTo(object? obj) takes a nullable value anyhow, we don't need a null check for the comparand. If the value returned by values1[^2] (accessing the second last element) were null then CompareTo() would not be evaluated and null > 0 will always evaluate to false for int?. This is why we don't need the explicit null checks in the if statement.

    The second line does exactly the same. Note that any comparison of int with null will return false. So null < 0 is false.