我知道这有点微不足道但是......
如果存在,那么获取引用的第一项是什么的最佳方法是什么?假设集合包含引用类型的项目。
代码示例1:
if (collection.Any())
{
var firstItem = collection.First();
// add logic here
}
上面的示例在集合上有两个单独的调用,开始迭代,一旦检测到第一个就完成迭代。
代码示例2:
var firstItem = collection.FirstOrDefault();
if (firstItem != null)
{
// add logic here
}
上面的示例只对集合进行了一次调用,但引入了一个不必要地在更广范围内的变量。
是否存在与此方案相关的最佳做法?有更好的解决方案吗?
答案 0 :(得分:6)
我更喜欢第二个例子,因为它在一般情况下更有效。这个集合可能是许多不同的延迟评估LINQ查询的组合,因此即使获得第一个元素也需要非常重要的工作量。
想象一下,例如,此集合是从以下LINQ查询构建的
var collection = originalList.OrderBy(someComparingFunc);
只获取collection
中的第一个元素需要完整的originalList
内容。每次评估collection
的元素时,都会发生这种完整排序。
第一个示例导致可能昂贵的集合被评估两次:通过Any
和First
方法。第二个样本只评估一次集合,因此我会在第一个样本中选择它。
答案 1 :(得分:3)
你可以创建一个像这样的扩展方法:
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
}
答案 2 :(得分:2)
第二个不适用于非可空值类型(编辑:,如您所假设 - 第一次错过)并且除了第一个之外没有其他选择,竞争条件。有两种选择都是合适的 - 选择一种或另一种取决于你获得空序列的频率。
如果这是一个常见或预期的情况,你得到一个空的枚举,使用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
}
}
答案 3 :(得分:1)
有时我使用这种模式:
foreach (var firstItem in collection) {
// add logic here
break;
}
它只启动一次迭代(因此它比代码示例1更好)并且变量firstItem
的范围限制在括号内(因此它比代码示例2更好)。
答案 4 :(得分:1)
或者,作为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));
答案 5 :(得分:0)
由于所有通用Collections
(即:System.Collections.ObjectModel类型}具有Count
成员,我的首选方式如下:
Item item = null;
if(collection.Count > 0)
{
item = collection[0];
}
这是安全的,因为所有集合都将具有Count
和Item
属性。对于阅读代码的任何其他程序员来说,它也非常直接且容易理解你的意图。
答案 6 :(得分:0)
刚刚对原始类型进行了简单测试,看起来您的代码示例#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秒