简体   繁体   中英

IEnumerable FirstOrEmpty Extension

Problem

I am looking for a way to implement FirstOrEmpty for a IEnumerable<X> where X implements IEnumerable<T> . Basically, if the predicate does not match anything, return Enumerable<T>.Empty . I do not want to constrain the source parameter to IEnumerable<IEnumerable<X>> or because I might want to pass in something that implement IEnumerable<X> (eg IEnumerable<IGrouping<bool, X>> ).

Usage Example

IEnumerable<T> myCollection;
var groupCheck = myCollection.GroupBy(t => t.SomeProp == 23);

var badGroup = groupCheck.FirstOrEmpty(t => !t.Key);
var goodGroup = groupCheck.FirstOrEmpty(t => t.Key);

foreach(T x in badGroup) { ... }
foreach(T x in goodGroup) { ... }

Old way:

IEnumerable<T> myCollection = ...;
var groupCheck = myCollection.GroupBy(t => t.SomePropOnClassT == 23);

var badGroup = (groupCheck.FirstOrDefault(t => !t.Key) ?? Enumerable<T>.Empty);
var goodGroup = (groupCheck.FirstOrDefault(t => t.Key) ?? Enumerable<T>.Empty);

foreach(T x in badGroup) { ... }
foreach(T x in goodGroup) { ... }

Attempts

Attempt 1:

public static IEnumerable<TResult> FirstOrEmpty<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate) where TSource : IEnumerable<TResult>
{
    TSource tmp = source.FirstOrDefault(predicate);
    if(tmp != null) {
        foreach(TResult x in tmp)
        {
            yield return x; 
        } 
    }
}

Attempt 2:

public static IEnumerable<TResult> FirstOrEmpty<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate) where TSource : IEnumerable<TResult>
{
    TSource tmp = source.FirstOrDefault(predicate);
    return tmp == null ? Enumerable.Empty<TResult>() : tmp;
}

Your own solution:

public static IEnumerable<TResult> FirstOrEmpty<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate
    )
    where TSource : class, IEnumerable<TResult>
{
    return source.FirstOrDefault(predicate) ?? Enumerable.Empty<TResult>();
}

is actually working. I only added the class constraint. This means " TSource must be a reference type", and an interface is OK with a class constraint. The reason for this is that default(TSource) might not be null if TSource were some struct. You can call it like this:

var badGroup = groupCheck.FirstOrEmpty<IGrouping<bool, T>, T>(g => !g.Key);
var goodGroup = groupCheck.FirstOrEmpty<IGrouping<bool, T>, T>(g => g.Key);

Unfortunately, the compiler isn't smart enough to figure out the type parameters itself, so you have to supply them in the angle brackets <..., ...> like above. I haven't found a solution for that.

Now, if you always use this with IGrouping<,> , you might want to use the following slightly less general method:

public static IEnumerable<TResult> FirstOrEmpty<TKey, TResult>(
    this IEnumerable<IGrouping<TKey, TResult>> source,
    Func<IGrouping<TKey, TResult>, bool> predicate
    )
{
    return source.FirstOrDefault(predicate) ?? Enumerable.Empty<TResult>();
}

This time it goes like this:

var badGroup = groupCheck.FirstOrEmpty<bool, T>(g => !g.Key);
var goodGroup = groupCheck.FirstOrEmpty<bool, T>(g => g.Key);

and the good news is that with this approach the compiler will infer your type arguments, so:

var badGroup = groupCheck.FirstOrEmpty(g => !g.Key);
var goodGroup = groupCheck.FirstOrEmpty(g => g.Key);

works.

I would probably use a refactored version of Attempt 2:

public static IEnumerable<TResult> FirstOrEmpty<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate) where TSource : class, IEnumerable<TResult>
{
    return source.FirstOrDefault(predicate) ?? Enumerable.Empty<TResult>();
}

EDIT: I am curious to know why this answer was downvoted.

I came up with the same result as @phoog but am having a hard time getting the compiler to infer the type parameters:

public static IEnumerable<TResult> FirstOrEmpty<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate) where TSource : IEnumerable<TResult>
{
    return (IEnumerable<TResult>)source.FirstOrDefault(predicate) ?? Enumerable.Empty<TResult>();
}

Best I have come up with is to explicitly state them:

var badGroup = groupCheck.FirstOrEmpty<IGrouping<bool,int>,int>(t => !t.Key);
var goodGroup = groupCheck.FirstOrEmpty<IGrouping<bool,int>,int>(t => t.Key);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM