Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a convenient way to filter a sequence of C# 8.0 nullable references, retaining only non-nulls?

I have code like this:

IEnumerable<string?> items = new [] { "test", null, "this" };
var nonNullItems = items.Where(item => item != null); //inferred as IEnumerable<string?>
var lengths = nonNullItems.Select(item => item.Length); //nullability warning here
Console.WriteLine(lengths.Max());

How can I write this code in a convenient way such that:

  • There is no nullability warning, because the type nonNullItems is inferred as IEnumerable<string>.
  • I don't need to add unchecked non-nullability assertions like item! (because I want to benefit from the compilers sanity checking, and not rely on me being an error-free coder)
  • I don't add runtime checked non-nullability assertions (because that's pointless overhead both in code-size and at runtime, and in case of human error that fails later than ideal).
  • The solution or coding pattern can apply more generally to other sequences of items of nullable-reference type.

I'm aware of this solution, which leverages the flow-sensitive typing in the C# 8.0 compiler, but it's.... not so pretty, mostly because it's so long and noisy:

var notNullItems = items.SelectMany(item => 
    item != null ? new[] { item } : Array.Empty<string>())
);

Is there a better alternative?

like image 673
Eamon Nerbonne Avatar asked Oct 14 '19 08:10

Eamon Nerbonne


2 Answers

Unfortunately you will have to tell the compiler that you know more about the situation than it does.

One reason would be that the Where method has not been annotated in a way that lets the compiler understand the guarantee for non-nullability, nor is it actually possible to annotate it. There might be a case for having additional heuristics added to the compiler to understand some basic cases, like this one, but currently we do not have it.

As such, one option would be to use the null forgiving operator, colloquially known as the "dammit operator". You touch upon this yourself, however, instead of sprinkling exclamation marks all over the code where you use the collection, you can instead tuck on an additional step on producing the collection which, at least to me, makes it more palatable:

var nonNullItems = items.Where(item => item != null).Select(s => s!);

This will flag nonNullItems as IEnumerable<string> instead of IEnumerable<string?>, and thus be handled correctly in the rest of your code.

like image 89
Lasse V. Karlsen Avatar answered Oct 21 '22 00:10

Lasse V. Karlsen


I think you'll have to help the compiler in either one way or another. Calling .Where() is never safe of returning not-null. Maybe Microsoft could add some logic to determine basic scenarios like yours, but AFAIK that's not the situation right now.

However, you could write a simple extension method like that:

public static class Extension
{
    public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> o) where T:class
    {
        return o.Where(x => x != null)!;
    }
}
like image 36
mu88 Avatar answered Oct 21 '22 00:10

mu88