I am doing something very simple and straightforward, and I cannot help but suspect / wish that it could be made a bit shorter.
For the sake of the example, let us suppose that I have an IEnumerable<int?>
and all I want is an IEnumerable<int>
yielding only the non-null values. Here is some code that works, but can it be made more terse?
public static void Main( string[] args )
{
int?[] a = { null, 42, null, 5 };
IEnumerable<int> ints = a //
.Where( i => i.HasValue ) //
.Select( i => i!.Value );
foreach( var i in ints )
Console.WriteLine( i );
Console.ReadLine();
}
Note that if it could be made more terse, then it would also be made more neat, because that ugly i!
could be avoided.
But also note that this is just an example, so please do not focus on the fact that these are nullable ints. They could just as easily be some complex objects which might be derived, and we are trying to obtain an enumeration of only those that are more derived, converted to the more derived type.
So, this question is how to do a Where()
and Select()
in one statement, so as to achieve conversion and filtering in one step, thus taking advantage of work done during filtering, to lessen the amount of work necessary during conversion.
2 Answers
2
Reset to default
Highest score (default)
Trending (recent votes count more)
Date modified (newest first)
Date created (oldest first)
If you have an IEnumerable<int?>
, then .OfType<int>()
returns IEnumerable<int>
and excludes elements that are null.
Similarly, if you have an IEnumerable<Base>
, then .OfType<Derived>()
returns IEnumerable<Derived>
and excludes objects of other types.
If you want to do something more complicated than filtering based on type, then you could write your own extension method:
public static IEnumerable<TResult> SelectWhere<TSource, TResult>(
this IEnumerable<TSource> source, Func<TSource, (bool, TResult)> selector)
{
foreach (TSource item in source)
if (selector(item) is (true, var result))
yield return result;
}
As an example, if .OfType<int>()
didn’t exist, you could use the following instead:
IEnumerable<int> ints = a.SelectWhere(n => n is int i ? (true, i) : (false, default));
It can be done in one step using LINQ’s SelectMany method, e.g.:
IEnumerable<int> ints = a
.SelectMany(e => e switch
{
int i => new[] { i },
_ => Enumerable.Empty<int>()
});
This works because SelectMany
allows us to create a new sequence of elements (possible of a different type) for every element of the input sequence (which then are simply concatenated by SelectMany
to produce the final result). Here we return a single-element sequence for every element we want to include and empty sequence for every element we want to discard.
For fun, if we go a bit deeper into theory, LINQ is an example of monad with SelectMany
serving the role of monad’s bind
operation (>>=
operator in Haskell). Which means that in fact we should be able to implement all LINQ’s methods using only SelectMany
method.
NB: The above implementation might not be the most efficient way to do it since we allocate a new single-element array for every element we want to include in the resulting sequence. And in fact that new[] { i }
expression if monad’s return
operator for which there is no corresponding method in LINQ. We can implement it ourselves easily though (just for fun):
public static class EnumerableExtension
{
public static IEnumerable<T> Return<T>(this T elem)
{
yield return elem;
}
}
This Return
might even improve the efficiency of our implementation:
IEnumerable<int> ints = a
.SelectMany(e => e switch
{
int i => i.Return(),
_ => Enumerable.Empty<int>()
});
Or without extension method:
IEnumerable<int> ints = a
.SelectMany(e => e switch
{
int i => Enumerable.Empty<int>().Prepend(i),
_ => Enumerable.Empty<int>()
});
Your Answer
Sign up or log in
Post as a guest
Required, but never shown
Post as a guest
Required, but never shown
By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy
Not the answer you're looking for? Browse other questions tagged
or ask your own question.
or ask your own question.
|