我有一个IEnumerable和一个谓词(Func),我正在编写一个方法,如果列表中只有一个实例与谓词匹配,则返回一个值。如果条件与none匹配,则找不到任何条件。如果标准与许多实例匹配,则谓词不足以成功识别所需记录。这两种情况都应该返回null。
在LINQ中表达这种情况的建议方法是什么,不会导致列表的多个枚举?
如果找到多个实例,LINQ运算符SingleOrDefault将抛出异常。 即使找到多个,LINQ运算符FirstOrDefault也将返回第一个。
MyList.Where(predicate).Skip(1).Any()
...将检查歧义,但不会保留所需的记录。
似乎我最好的举动是从MyList.Where(谓词)中获取Enumerator并保留第一个实例,如果访问下一个项目失败,但它似乎有点冗长。
我错过了一些明显的东西吗?
答案 0 :(得分:3)
"稍微冗长"选项对我来说似乎是合理的,并且可以很容易地被分离为单个扩展方法:
// 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;
}
}
答案 1 :(得分:0)
更新:这是一种更通用的方法,可能更具可重用性。
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()