Why can't I use a nullable tuple for a nullable generic parameter

Viewed 353

So I wondered if you could create an extension method that would combine the functionality of .Where() and .Select() for the special case that it filters out null values of a list and keeps the types consistent with Nullable Reference Types. This means if I got an string?[] it would filter out all null values and return an IEnumerable<string> without nullable strings.

However, when I try to filter out nullable tuples as shown below, I get the compiler error that the non-nullable tuple type was expected. Why does this solution work for all conventional types yet not for tuples and how can I change my extension method to also work for tuples.

Extension Method:

public static IEnumerable<TResult> SelectNotNull<TSource, TResult>
(this IEnumerable<TSource> enumerable, Func<TSource, TResult?> selector)
{
    foreach (var item in enumerable)
        if (selector(item) is { } result)
            yield return result;
}

Sample Code that produces the compiler error:

(string?, int)[] arr = Array.Empty<(string?, int)>();

static (string, int)? Selector((string?, int) x)
{
    return x.Item1 != null ? x : null;
}

arr.SelectNotNull<(string?, int), (string, int)>(Selector);
1 Answers

Without a constraint, your TResult? is interpreted as a reference-type nullability annotation (and you should see a CS8632 warning if reference-type nullability isn't enabled, or a hard error if using earlier C# compiler versions) - and nullability annotations are very much just advisory; they don't change any hard behaviours. To get the behaviour you seem to want with value types, you can add:

public static IEnumerable<TResult> SelectNotNull<TSource, TResult>
(this IEnumerable<TSource> enumerable, Func<TSource, TResult?> selector)
    where TResult : struct // <== add constraint

However, this then won't work with reference types at all.

Related