IEnumerable<T?> doesn't get converted to IEnumerable<T> in extension method

101 Views Asked by At

I have the following extension methods:

public static IEnumerable<T?> From<T>(params T?[] values) {
    return values.AsEnumerable();
}

public static IEnumerable<T> WhereNonNull<T>(this IEnumerable<T?> enumerable)
{
    return enumerable.Where(x => x != null).Cast<T>();
}

I use it like this:

uint? parentId = ...;
int b = ...;
foreach(var boneId in CollectionUtils.From(parentId, (uint)b).WhereNonNull())

parentId is of type uint? and I would expect boneId to be of type uint due to the Cast<T>(), but it actually is uint?.

Am I missing something?

1

There are 1 best solutions below

3
Christian Held On BEST ANSWER

You can achieve the desired behavior with the struct and notnull constraints.

See Constraints on type parameters for details

For uint? change the WhereNotNull method to:

public static IEnumerable<T> WhereNonNull<T>(this IEnumerable<T?> enumerable)
    where T : struct
{
    return enumerable.Where(x => x != null).Cast<T>();
}

To support reference types as well also add

public static IEnumerable<T> WhereNonNull<T>(this IEnumerable<T?> enumerable)
    where T : notnull
{
    return enumerable.Where(x => x != null).Cast<T>();
}

It all comes down to the difference between nullable value types (Nullable<T> or T? for structs) and nullable reference types T? (like object?):

The struct constraint will make sure that T must be a non-nullable value type. So in this case T? means Nullable<T>

In notnull constraint that the value will be a non-nullable reference or non-nullable value type. The behavior of this method is the same as the version without constraints when there is no overload with struct constraint available. You would only need it if you want to support both Nullable<T> and nullable reference types at the same time.