问题
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 asIEnumerable<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?
回答1:
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)!;
}
}
回答2:
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.
回答3:
I don't know if this answer meets the criteria for your 3rd bullet point, but then your .Where()
filter does not either, so...
Replace
var nonNullItems = items.Where(item => item != null)
with
var nonNullItems = items.OfType<string>()
This will yield an inferred type of IEnumerable<string>
for nonNullItems
, and this technique can be applied to any nullable reference type.
来源:https://stackoverflow.com/questions/58372791/is-there-a-convenient-way-to-filter-a-sequence-of-c-sharp-8-0-nullable-reference