繁体   English   中英

检查和检索集合的第一项的最佳方法是什么?

[英]What is the best way to check and retrieve the first item of a collection?

我明白这有点微不足道但......

如果存在,那么获取引用的最佳方法是什么? 假设集合包含引用类型的项。

代码示例1:

if (collection.Any())
{
    var firstItem = collection.First();
    // add logic here
}

上面的示例在集合上有两个单独的调用,开始迭代,一旦检测到第一个就完成。

代码示例2:

var firstItem = collection.FirstOrDefault();
if (firstItem != null)
{
    // add logic here
}

上面的示例只对集合进行了一次调用,但引入了一个不必要地在更广范围内的变量。

是否存在与此方案相关的最佳实践? 有更好的解决方案吗?

我更喜欢第二个例子,因为它在一般情况下更有效。 这个集合可能是许多不同的延迟评估LINQ查询的组合,因此即使获得第一个元素也需要非常重要的工作量。

例如,想象一下这个集合是从以下LINQ查询构建的

var collection = originalList.OrderBy(someComparingFunc);

collection获取第一个元素需要完整的originalList内容。 每次评估collection元素时都会发生这种完整排序。

第一个样本导致可能昂贵的集合被评估两次:通过AnyFirst方法。 第二个样本只评估一次集合,因此我会在第一个样本中选择它。

您可以创建一个这样的扩展方法:

public static bool TryGetFirst<T>(this IEnumerable<T> seq, out T value)
{
    foreach (T elem in seq)
    {
        value = elem;
        return true;
    }
    value = default(T);
    return false;
}

然后你会像这样使用它:

int firstItem;
if (collection.TryGetFirst(out firstItem))
{
    // do something here
}

第二个不适用于非可空值的类型( 编辑:正如您所假设的那样 - 第一次错过了)并且除了第一个之外没有其他选择,它具有竞争条件。 有两种选择都是合适的 - 选择一种或另一种取决于您获得空序列的频率。

如果它是一个常见或预期的情况,你得到一个空的枚举,使用foreach循环是相对整洁的:

foreach (var firstItem in collection)
{
    // add logic here
    break;
}

或者如果你真的不想在那里break (这是可以理解的):

foreach (var firstItem in collection.Take(1))
{
    // add logic here
}

如果它是空的相对不常见,那么try/catch块应该提供最佳性能(因为异常只有在它们实际被提升时才是昂贵的 - 一个非增加的异常实际上是免费的):

try
{
    var firstItem = collection.First();
    // add logic here
}
catch (InvalidOperationException) { }

第三种选择是直接使用枚举器,虽然这应该与foreach版本相同,但不太清楚:

using (var e = collection.GetEnumerator())
{
    if (e.MoveNext())
    {
        var firstItem = e.Current;
        // add logic here
    }
}

有时我使用这种模式:

foreach (var firstItem in collection) {
    // add logic here
    break;
}

它只启动一次迭代(因此它比代码示例1更好)并且变量firstItem的范围限制在括号内(因此它比代码示例2更好)。

或者,作为Gabe解决方案的扩展,让它使用lambda,这样你就可以删除if:

public static class EnumerableExtensions
{
    public static bool TryGetFirst<T>(this IEnumerable<T> seq, Action<T> action)
    {
        foreach (T elem in seq)
        {
            if (action != null)
            {
                action(elem);
            }

            return true;
        }

        return false;
    }
}

并使用它像:

     List<int> ints = new List<int> { 1, 2, 3, 4, 5 };

     ints.TryGetFirst<int>(x => Console.WriteLine(x));

由于所有通用Collections (即:类型为System.Collections.ObjectModel )都具有Count成员,因此我首选的方法如下:

Item item = null;
if(collection.Count > 0)
{
    item = collection[0];
}

这是安全的,因为所有集合都将具有CountItem属性。 对于阅读代码的任何其他程序员来说,它也非常直接且易于理解您的意图。

刚刚对原始类型进行了简单测试,看起来你的代码示例#2在这种情况下最快(更新):

[TestFixture] public class SandboxTesting {
  #region Setup/Teardown
  [SetUp] public void SetUp() {
    _iterations = 10000000;
  }
  [TearDown] public void TearDown() {}
  #endregion
  private int _iterations;
  private void SetCollectionSize(int size) {
    _collection = new Collection<int?>();
    for(int i = 0; i < size; i++)
      _collection.Add(i);
  }
  private Collection<int?> _collection;
  private void AnyFirst() {
    if(_collection.Any()) {
      int? firstItem = _collection.First();
      var x = firstItem;
    }
  }
  private void NullCheck() {
    int? firstItem = _collection.FirstOrDefault();
    if (firstItem != null) {
      var x = firstItem;
    }
  }
  private void ForLoop() {
    foreach(int firstItem in _collection) {
      var x = firstItem;
      break;
    }
  }
  private void TryGetFirst() {
    int? firstItem;
    if (_collection.TryGetFirst(out firstItem)) {
      var x = firstItem;
    }
  }    
  private TimeSpan AverageTimeMethodExecutes(Action func) {
    // clean up
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    // warm up 
    func();

    var watch = Stopwatch.StartNew();
    for (int i = 0; i < _iterations; i++) {
      func();
    }
    watch.Stop();
    return new TimeSpan(watch.ElapsedTicks/_iterations);
  }
  [Test] public void TimeAnyFirstWithEmptySet() {      
    SetCollectionSize(0);

    TimeSpan averageTime = AverageTimeMethodExecutes(AnyFirst);

    Console.WriteLine("Took an avg of {0} secs on empty set", avgTime);     
  }
  [Test] public void TimeAnyFirstWithLotsOfData() {
    SetCollectionSize(1000000);

    TimeSpan avgTime = AverageTimeMethodExecutes(AnyFirst);

    Console.WriteLine("Took an avg of {0} secs on non-empty set", avgTime);      
  }
  [Test] public void TimeForLoopWithEmptySet() {
    SetCollectionSize(0);

    TimeSpan avgTime = AverageTimeMethodExecutes(ForLoop);

    Console.WriteLine("Took an avg of {0} secs on empty set", avgTime);
  }
  [Test] public void TimeForLoopWithLotsOfData() {
    SetCollectionSize(1000000);

    TimeSpan avgTime = AverageTimeMethodExecutes(ForLoop);

    Console.WriteLine("Took an avg of {0} secs on non-empty set", avgTime);
  }
  [Test] public void TimeNullCheckWithEmptySet() {
    SetCollectionSize(0);

    TimeSpan avgTime = AverageTimeMethodExecutes(NullCheck);

    Console.WriteLine("Took an avg of {0} secs on empty set", avgTime);
  }
  [Test] public void TimeNullCheckWithLotsOfData() {
    SetCollectionSize(1000000);

    TimeSpan avgTime = AverageTimeMethodExecutes(NullCheck);

    Console.WriteLine("Took an avg of {0} secs on non-empty set", avgTime);
  }
  [Test] public void TimeTryGetFirstWithEmptySet() {
    SetCollectionSize(0);

    TimeSpan avgTime = AverageTimeMethodExecutes(TryGetFirst);

    Console.WriteLine("Took an avg of {0} secs on empty set", avgTime);
  }
  [Test] public void TimeTryGetFirstWithLotsOfData() {
    SetCollectionSize(1000000);

    TimeSpan averageTime = AverageTimeMethodExecutes(TryGetFirst);

    Console.WriteLine("Took an avg of {0} secs on non-empty set", avgTime);
  }
}
public static class Extensions {
  public static bool TryGetFirst<T>(this IEnumerable<T> seq, out T value) {
    foreach(T elem in seq) {
      value = elem;
      return true;
    }
    value = default(T);
    return false;
  }
}

AnyFirst
NonEmpty:00:00:00.0000262秒
EmptySet:00:00:00.0000174秒

for循环
NonEmpty:00:00:00.0000158秒
EmptySet:00:00:00.0000151秒

NullCheck
NonEmpty:00:00:00.0000088秒
EmptySet:00:00:00.0000064秒

TryGetFirst
NonEmpty:00:00:00.0000177秒
EmptySet:00:00:00.0000172秒

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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