public static IEnumerable<TR> SelectNotNull<T, TR>(
this IEnumerable<T> source,
Func<T, TR?> fn)
where TR : class
{
return source.Select(fn)
.Where(it => it != null)
.OfType<T>();
}
public static IEnumerable<TR> SelectNotNull<T, TR>(
this IEnumerable<T> source,
Func<T, TR?> fn)
where TR : struct
{
return source.Select(fn)
.Where(it => it != null)
.Select(item => item.Value);
}
}
No, same concept ... you're making a mistake that some call "implementation on the brain". That they're the same concept is why you're able to specify a common operation, SelectNotNull. That you had to provide an explicit type constraint that a compiler should be able to infer doesn't change that.
This is why it's absolutely essential to always be aware of which types are value types in the .NET type system. Attempts by either users or the designers to conflate or unify them are ill-conceived. The nullability semantics could have been kept consistent if they went all the way back to the beginning of the CLR and did it that way, but this would have not been consistent with Java's JVM and type system (which they were trying to mimic).
That said, we already have value types like System.Int32 which inherit from System.ValueType (an abstract type) which inherits from System.Object (a non-abstract reference type), so things are already a bit weird.
As a side note: please avoid, as much as possible, putting `.Select(..)` before `.Where(..)`. You are wasting CPU cycles and memory space by forcing LINQ to map all the items and then filtering on the mapped value.
In most situations, you should be able to filter on the source enumerable before mapping making the whole thing more efficient.
Additionally, that `.Cast<TR>(..)` at the end should have been a dead giveaway that you are going down the wrong path here. You are incurring even more CPU and Memory costs as the `.Cast<TR>(..)` call will now iterate through all the items needlessly.[1]
Also, the design of this this method doesn't seem to make much difference to me anyways:
```
var strs = source.SelectNotNull(it => it);
```
vs
```
var strs = source.Where(it => it != null);
```
A lot of other LINQ extension methods allow you to pass in a predicate expression that will be executed on the source enumerable:
> Luckily, since type constraints are part of the signature of the method and there is no ambiguity, I am allowed to make this overload.
This isn’t the case. It’s allowed because the question-mark syntax means two different things in value- and reference-type contexts. The signatures really look like this:
public static IEnumerable<TR> SelectNotNull<T, TR>(
this IEnumerable<T> source,
Func<T, TR> fn)
where TR : class // …and the nullability of TR is tracked by the compiler
public static IEnumerable<TR> SelectNotNull<T, TR>(
this IEnumerable<T> source,
Func<T, Nullable<TR>> fn)
where TR : struct
Uhm: There is the OfType method that is generally used to filter an IEnumerable<T?> to IEnumerable<T>. It doesn't care about reference vs value types, because it doesn't compare to null. Instead it checks the type of each element.
Why? If you have an value, foo, that's declared "int?", (foo is int) evaluates to if there is a value present, and false if there is no value present. The same thing happens if foo is declared as string?.
BTW: I checked if the overload can be avoided by using the "default" keyword. It can't.
9 comments
[ 5.7 ms ] story [ 28.2 ms ] threadpublic static class EnumerableExtensions {
public static IEnumerable<TR> SelectNotNull<T, TR>( this IEnumerable<T> source, Func<T, TR?> fn) where TR : class { return source.Select(fn) .Where(it => it != null) .OfType<T>(); }
public static IEnumerable<TR> SelectNotNull<T, TR>( this IEnumerable<T> source, Func<T, TR?> fn) where TR : struct { return source.Select(fn) .Where(it => it != null) .Select(item => item.Value); } }
No, same concept ... you're making a mistake that some call "implementation on the brain". That they're the same concept is why you're able to specify a common operation, SelectNotNull. That you had to provide an explicit type constraint that a compiler should be able to infer doesn't change that.
That said, we already have value types like System.Int32 which inherit from System.ValueType (an abstract type) which inherits from System.Object (a non-abstract reference type), so things are already a bit weird.
In most situations, you should be able to filter on the source enumerable before mapping making the whole thing more efficient.
Additionally, that `.Cast<TR>(..)` at the end should have been a dead giveaway that you are going down the wrong path here. You are incurring even more CPU and Memory costs as the `.Cast<TR>(..)` call will now iterate through all the items needlessly.[1]
Also, the design of this this method doesn't seem to make much difference to me anyways:
``` var strs = source.SelectNotNull(it => it); ```
vs
``` var strs = source.Where(it => it != null); ```
A lot of other LINQ extension methods allow you to pass in a predicate expression that will be executed on the source enumerable:
``` var str = source.First(it => it != null); ```
[1] https://source.dot.net/#System.Linq/System/Linq/Cast.cs,152b...
This isn’t the case. It’s allowed because the question-mark syntax means two different things in value- and reference-type contexts. The signatures really look like this:
This is an allowable overload.Why? If you have an value, foo, that's declared "int?", (foo is int) evaluates to if there is a value present, and false if there is no value present. The same thing happens if foo is declared as string?.
BTW: I checked if the overload can be avoided by using the "default" keyword. It can't.