检查IEnumerable是否只有一个元素的最佳方法是什么?

时间:2017-12-15 10:50:50

标签: c# linq

Count()会扫描所有元素,因此如果可枚举包含大量元素,则if (list.Count() == 1)将无法正常运行。

如果没有一个元素,

Single()会抛出异常。使用try { list.Single(); } catch(InvalidOperationException e) {}是笨拙且低效的。

如果有多个元素,则

SingleOrDefault()抛出异常,因此if (list.SingleOrDefault() == null)(假设TSource具有引用类型)将不适用于大小大于1的可枚举数。

7 个答案:

答案 0 :(得分:14)

var exactlyOne = sequence.Take(2).Count() == 1;

如果元素较少,则Take扩展方法不会抛出,只返回可用的元素。

答案 1 :(得分:9)

更直接:

public static bool HasSingle<T>(this IEnumerable<T> sequence) {
    if (sequence is ICollection<T> list) return list.Count == 1; // simple case
    using(var iter = sequence.GetEnumerator()) {
        return iter.MoveNext() && !iter.MoveNext();
    }
}

但请注意,您只能保证一次读取序列,因此在这些情况下:通过检查单个的简单事实项目,你不能再获得该项目。所以如果有的话,你可能更喜欢能给你价值的东西:

public static bool HasSingle<T>(this IEnumerable<T> sequence, out T value)
{
    if (sequence is IList<T> list)
    {
        if(list.Count == 1)
        {
            value = list[0];
            return true;
        }
    }
    else
    {
        using (var iter = sequence.GetEnumerator())
        {
            if (iter.MoveNext())
            {
                value = iter.Current;
                if (!iter.MoveNext()) return true;
            }
        }
    }

    value = default(T);
    return false;
}

答案 2 :(得分:5)

您可以使用!Skip(1).Any()

bool contains1 = items.Any() && !items.Skip(1).Any();

如果类型是集合,您可以创建一个更有效的扩展:

public static bool ContainsCountItems<TSource>(this IEnumerable<TSource> source, int count)
{
    ICollection<TSource> collectionoft = source as ICollection<TSource>;
    if (collectionoft != null) return collectionoft.Count == count;
    ICollection collection = source as ICollection;
    if (collection != null) return collection.Count == count;
    int itemCount = 0;
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
        checked
        {
            while (e.MoveNext() && ++itemCount <= count)
            {
                if (itemCount == count)
                    return !e.MoveNext();
            }
        }
    }
    return false;
}

用法:

var items = Enumerable.Range(0, 1);
bool contains1 = items.ContainsCountItems(1); // true;
items = Enumerable.Range(0, 2);
contains1 = items.ContainsCountItems(1); // false;

您可以将此扩展程序用于任何类型和任何计数,因此不仅仅是1

var items = Enumerable.Range(0, 10);
bool contains10 = items.ContainsCountItems(10); // true;

答案 3 :(得分:5)

我建议使用Any,我们必须检查

  1. list至少有一项 - Any
  2. list没有第二项 - !list.Skip(1).Any()
  3. 代码:

      bool isSingle = list.Any() && !list.Skip(1).Any();
    

    但是这种方法有一个缺点:它会扫描list 两次,这可能是IQueryable的问题(查询执行两次次)可能有不同的结果和额外的开销)

答案 4 :(得分:4)

为了避免在其他答案中进行额外的迭代,您可以实现自己的扩展:

public static bool HasExactlyOneElement<T>(this IEnumerable<T> source)
{
    using (var enumerator = source.GetEnumerator())
        return enumerator.MoveNext() && !enumerator.MoveNext();
}

答案 5 :(得分:2)

您调用原始枚举的每个Linq方法调用(.Any().Skip(),...)都会创建一个枚举器,根据您的要求,该枚举器也可能会受到相当大的性能影响。

所以你可以使用.Take(2).Count() == 1

另见There's a most performant way to check that a collection has exactly 1 element?

答案 6 :(得分:0)

如果您因任何原因不想使用Linq,您也可以手动使用Enumerator

/// <summary>
/// Checks that the IEnumerable&lt;T&gt; has exactly one item
/// </summary>
public static bool HasSingleElement<T>(IEnumerable<T> value)
{
    using ( var enumerator = value.GetEnumerator() )
    {
        // Try to get first element - return false if that doesn't exist
        if ( !enumerator.MoveNext() )
            return false;

        // Try to get second element - return false if it does exist
        if ( enumerator.MoveNext() )
            return false;

        // exactly one element exists
        return true;
    }
}