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

发布于 2024-10-19 14:09:23 字数 505 浏览 2 评论 0原文

我知道这有点微不足道,但是...

获取集合的第一项(如果存在)的参考的最佳方法是什么?假设集合包含引用类型的项目。

代码示例 1:

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

上面的示例对集合进行了两次单独的调用,开始迭代,一旦检测到第一个迭代,迭代就会完成。

代码示例 2:

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

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

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

I understand this is somewhat trivial but...

What the best way to get the reference the first item of a collection if any exist? Assume the collection contains items of a reference-type.

Code Sample 1:

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

The above sample has two separate calls on the collection starting an iteration which complete as soon as the first is detected.

Code Sample 2:

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

The above sample only has a single call on the collection but introduces a variable that is unnecessarily in a wider scope.

Is there a best-practices related to this scenario? Is there a better solution?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

如梦 2024-10-26 14:09:23

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

例如,假设该集合是根据以下 LINQ 查询构建的,

var collection = originalList.OrderBy(someComparingFunc);

仅从 collection 中获取第一个元素需要对 originalList 的内容进行完整排序。每次计算 collection 的元素时都会发生这种完整排序。

第一个示例导致潜在昂贵的集合被评估两次:通过 AnyFirst 方法。第二个示例仅评估集合一次,因此我会选择它而不是第一个。

I prefer the second example because it's more effecient in the general case. It's possible that this collection is combination of many different delay evaluated LINQ queries such that even getting the first element requires a non-trivial amount of work.

Imagine for example that this collection is build from the following LINQ query

var collection = originalList.OrderBy(someComparingFunc);

Getting just the first element out of collection requires a full sort of the contents of originalList. This full sort will occur each time the elements of collection are evaluated.

The first sample causes the potentially expensive collection to be evaluated twice: via the Any and First method. The second sample only evaluates the collection once and hence I would choose it over the first.

一直在等你来 2024-10-26 14:09:23

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

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
}

You could create an extension method like this:

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

Then you would use it like this:

int firstItem;
if (collection.TryGetFirst(out firstItem))
{
    // do something here
}
悲念泪 2024-10-26 14:09:23

第二个不适用于不可为 null 的值类型(编辑: 正如您所假设的 - 第一次错过了),并且除了第一个(具有竞争条件)之外实际上没有其他选择。有两种合适的替代方案 - 选择其中一种取决于您获得空序列的频率。

如果这是一种常见或预期的情况,您得到一个空枚举,那么使用 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
    }
}

The second doesn't work on non-nullable value types (Edit: as you assumed - missed that the first time) and doesn't really have an alternative besides the first, which has a race-condition. There are two alternatives which are both suitable - selecting one or the other depends on how frequently you will get an empty sequence.

If it's a common or expected case where you get an empty enumeration, using a foreach loop is relatively neat:

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

or if you really don't want the break in there (which is understandable):

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

If it is relatively unusual for it to be empty then a try/catch block should give the best performance (since exceptions are only expensive if they are actually raised - an unraised exception is practically free):

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

A third option is to use an enumerator directly, though this should be identical to the foreach version and is slightly less clear:

using (var e = collection.GetEnumerator())
{
    if (e.MoveNext())
    {
        var firstItem = e.Current;
        // add logic here
    }
}
比忠 2024-10-26 14:09:23

有时我会使用这种模式:

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

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

Sometimes I use this pattern:

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

It initiates only one iteration (so it's better than Code Sample 1) and the scope of the variable firstItem is limited inside the brackets (so it's better than Code Sample 2).

油饼 2024-10-26 14:09:23

或者,作为 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));

Or, as an extension to the solution from Gabe, make it use a lambda so you can drop the 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;
    }
}

And use it like:

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

     ints.TryGetFirst<int>(x => Console.WriteLine(x));
北风几吹夏 2024-10-26 14:09:23

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

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

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

Since all generic Collections (ie: of type System.Collections.ObjectModel) have the Count member my preferred way of doing this is as follows:

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

This is safe is since all Collections will have both the Count and Item property. Its also very straight forward and easy for any other programmers reading your code to understand what your intent is.

最佳男配角 2024-10-26 14:09:23

刚刚对原始类型进行了简单的测试,看起来您的代码示例 #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
非空:00:00:00.0000262 秒
EmptySet: 00:00:00.0000174 秒

ForLoop
非空:00:00:00.0000158 秒
EmptySet: 00:00:00.0000151 秒

NullCheck
非空:00:00:00.0000088 秒
EmptySet: 00:00:00.0000064 秒

TryGetFirst
非空:00:00:00.0000177 秒
空集:00:00:00.0000172 秒

Just did simple test on primitive type, and looks like your code sample #2 is fastest in this case (updated):

[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 seconds
EmptySet: 00:00:00.0000174 seconds

ForLoop
NonEmpty: 00:00:00.0000158 seconds
EmptySet: 00:00:00.0000151 seconds

NullCheck
NonEmpty: 00:00:00.0000088 seconds
EmptySet: 00:00:00.0000064 seconds

TryGetFirst
NonEmpty: 00:00:00.0000177 seconds
EmptySet: 00:00:00.0000172 seconds

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文