3

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> (e.g. 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;
}
4

3 回答 3

4

您自己的解决方案:

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>();
}

实际上正在工作。我只添加了class约束。这意味着“TSource必须是引用类型”,并且接口可以带有class约束。这样做的原因是,如果是某种结构,则default(TSource)可能不是。你可以这样称呼它:nullTSource

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

不幸的是,编译器不够聪明,无法判断类型参数本身,因此您必须<..., ...>像上面一样在尖括号中提供它们。我还没有找到解决方案。

现在,如果您总是将它与 一起使用IGrouping<,>,您可能希望使用以下不太通用的方法:

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>();
}

这次是这样的:

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

好消息是,通过这种方法,编译器推断您的类型参数,因此:

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

作品。

于 2012-12-10T16:51:19.177 回答
2

我可能会使用尝试 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>();
}

编辑:我很想知道为什么这个答案被否决了。

于 2012-12-06T19:47:33.147 回答
1

我想出了与@phoog 相同的结果,但很难让编译器推断类型参数:

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>();
}

我想出的最好的办法是明确说明它们:

var badGroup = groupCheck.FirstOrEmpty<IGrouping<bool,int>,int>(t => !t.Key);
var goodGroup = groupCheck.FirstOrEmpty<IGrouping<bool,int>,int>(t => t.Key);
于 2012-12-06T19:36:19.293 回答