我有一个IEnumerable和一个谓词(Func),我正在编写一个方法,如果列表中只有一个实例与谓词匹配,则返回一个值。如果没有匹配条件,则没有找到任何条件。如果条件与许多实例相匹配,则谓词不足以成功标识所需的记录。这两种情况都应该返回null。
在LINQ中不导致列表的多个枚举的推荐表达方式是什么?
如果找到多个实例,LINQ操作符SingleOrDefault将抛出异常。LINQ操作符FirstOrDefault将返回第一个,即使找到多个。
MyList.Where(predicate).Skip(1).Any()
…将检查歧义,但不保留所需的记录。
似乎我最好的举动是从MyList.Where(谓词)中抓取Enumerator并保留第一个实例,如果访问下一个项目失败,但它似乎有点冗长。
我错过了什么明显的吗?
"稍微冗长"选项对我来说似乎是合理的,并且可以很容易地分离成单个扩展方法:
// TODO: Come up with a better name :)
public static T SingleOrDefaultOnMultiple<T>(this IEnumerable<T> source)
{
// TODO: Validate source is non-null
using (var iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
{
return default(T);
}
T first = iterator.Current;
return iterator.MoveNext() ? default(T) : first;
}
}
Update:这是一种更通用的方法,可能更易于重用。
public static IEnumerable<TSource> TakeIfCountBetween<TSource>(this IEnumerable<TSource> source, int minCount, int maxCount, int? maxTake = null)
{
if (source == null)
throw new ArgumentNullException("source");
if (minCount <= 0 || minCount > maxCount)
throw new ArgumentException("minCount must be greater 0 and less than or equal maxCount", "minCount");
if (maxCount <= 0)
throw new ArgumentException("maxCount must be greater 0", "maxCount");
int take = maxTake ?? maxCount;
if (take > maxCount)
throw new ArgumentException("maxTake must be lower or equal maxCount", "maxTake");
if (take < minCount)
throw new ArgumentException("maxTake must be greater or equal minCount", "maxTake");
int count = 0;
ICollection objCol;
ICollection<TSource> genCol = source as ICollection<TSource>;
if (genCol != null)
{
count = genCol.Count;
}
else if ((objCol = source as ICollection) != null)
{
count = objCol.Count;
}
else
{
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext() && ++count < maxCount);
}
}
bool valid = count >= minCount && count <= maxCount;
if (valid)
return source.Take(take);
else
return Enumerable.Empty<TSource>();
}
用法:
var list = new List<string> { "A", "B", "C", "E", "E", "F" };
IEnumerable<string> result = list
.Where(s => s == "A")
.TakeIfCountBetween(1, 1);
Console.Write(string.Join(",", result)); // or result.First()