繁体   English   中英

为什么我要使用 Enumerable.ElementAt() 而不是 [] 运算符?

[英]Why would I use Enumerable.ElementAt() versus the [] operator?

这似乎是一个愚蠢的问题,但我还没有找到答案,所以就在这里。 :)

在这两种情况下,如果您未能检查集合的边界,您将收到“超出范围”的异常。 这只是编码风格偏好吗?

如果有人需要一个例子:

List<byte> myList = new List<byte>(){0x01, 0x02, 0x03};
byte testByte = myList.ElementAt(2);

相对

byte testByte = myList[2];

因为Enumerable更通用,并且由 enumerable 表示的集合可能没有索引器。

但是,如果确实如此 - 不要使用ElementAt()它可能不会那么有效。

ElementAt()为 C# 中的所有枚举提供了统一的接口。 我倾向于经常使用它,因为我喜欢通用 API。

如果底层类型支持随机访问(即,它支持[]运算符),则 ElementAt 将使用它。 所以唯一的开销是额外的方法调用(这几乎从不相关)。

如果底层类型不支持随机访问,则ElementAt()通过迭代枚举来模拟它,直到它到达您关心的元素。 这可能非常昂贵,有时甚至会产生副作用。

还有ElementAtOrDefault()通常非常方便。

避免在某些情况下使用 ElementAt() !!!

如果您知道要查找每个元素,并且您有(或可能有)超过 500 个元素,则只需调用 ToArray(),将其存储在可重用的数组变量中,然后以这种方式对其进行索引。

例如; 我的代码正在从 Excel 文件中读取数据。
我正在使用 ElementAt() 来查找我的 Cell 所引用的 SharedStringItem。
对于 500 行或更少的行,您可能不会注意到其中的差异。
对于 16K 行,需要 100 秒。

更糟糕的是(读取的每一行)索引越大,每次迭代时它必须索引的越多,所以它花费的时间比它应该的要长。
前 1,000 行需要 2.5 秒,而最后 1,000 行需要 10.7 秒。

循环这行代码:

SharedStringItem ssi = sst.Elements<SharedStringItem>().ElementAt(ssi_index);

导致此记录输出:

...Using ElementAt():
RowIndex:  1000 Read: 1,000 Seconds: 2.4627589
RowIndex:  2000 Read: 1,000 Seconds: 2.9460492
RowIndex:  3000 Read: 1,000 Seconds: 3.1014865
RowIndex:  4000 Read: 1,000 Seconds: 3.76619
RowIndex:  5000 Read: 1,000 Seconds: 4.2489844
RowIndex:  6000 Read: 1,000 Seconds: 4.7678506
RowIndex:  7000 Read: 1,000 Seconds: 5.3871863
RowIndex:  8000 Read: 1,000 Seconds: 5.7997721
RowIndex:  9000 Read: 1,000 Seconds: 6.4447562
RowIndex: 10000 Read: 1,000 Seconds: 6.8978011
RowIndex: 11000 Read: 1,000 Seconds: 7.4564455
RowIndex: 12000 Read: 1,000 Seconds: 8.2510054
RowIndex: 13000 Read: 1,000 Seconds: 8.5758217
RowIndex: 14000 Read: 1,000 Seconds: 9.2953823
RowIndex: 15000 Read: 1,000 Seconds: 10.0159931
RowIndex: 16000 Read: 1,000 Seconds: 10.6884988
Total Seconds: 100.6736451

一旦我创建了一个中间数组来存储 SharedStringItem 以供参考,我的时间就从 100 秒减少到 10 秒,现在在相同的时间内处理每一行。

这行代码:

SharedStringItem[] ssia = sst == null ? null : sst.Elements<SharedStringItem>().ToArray();
Console.WriteLine("ToArray():" + watch.Elapsed.TotalSeconds + " Len:" + ssia.LongCount());

并循环这行代码:

SharedStringItem ssi = ssia[ssi_index];

导致此记录输出:

...Using Array[]:
ToArray(): 0.0840583 Len: 33560
RowIndex:  1000 Read: 1,000 Seconds: 0.8057094
RowIndex:  2000 Read: 1,000 Seconds: 0.8183683
RowIndex:  3000 Read: 1,000 Seconds: 0.6809131
RowIndex:  4000 Read: 1,000 Seconds: 0.6530671
RowIndex:  5000 Read: 1,000 Seconds: 0.6086124
RowIndex:  6000 Read: 1,000 Seconds: 0.6232579
RowIndex:  7000 Read: 1,000 Seconds: 0.6369397
RowIndex:  8000 Read: 1,000 Seconds: 0.629919
RowIndex:  9000 Read: 1,000 Seconds: 0.633328
RowIndex: 10000 Read: 1,000 Seconds: 0.6356769
RowIndex: 11000 Read: 1,000 Seconds: 0.663076
RowIndex: 12000 Read: 1,000 Seconds: 0.6633178
RowIndex: 13000 Read: 1,000 Seconds: 0.6580743
RowIndex: 14000 Read: 1,000 Seconds: 0.6518182
RowIndex: 15000 Read: 1,000 Seconds: 0.6662199
RowIndex: 16000 Read: 1,000 Seconds: 0.6360254
Total Seconds: 10.7586264

如您所见,将 33,560 个项目转换为数组只需要几分之一秒,这对于加快我的导入过程来说是非常值得的。

更新:我觉得这是一个有趣的话题,并决定写博客

基本上,我的观点是,在不需要IList<T>情况下利用抽象进行随机访问几乎没有意义。 如果您正在编写的代码想要通过索引随机访问集合的元素,那么只需要一个能够为您提供的代码。 如果你需要一个IList<T>开始,我真的认为使用ElementAt而不是索引器没有意义。

但这当然只是我的意见。


不要将它用于IList<T>实现,如T[]List<T> 仅当您需要不提供随机访问的集合类型时才使用它,例如Queue<T>Stack<T>

var q = new Queue<int>();
var s = new Stack<int>();
for (int i = 0; i < 10; ++i)
{
    q.Enqueue(i);
    s.Push(i);
}

// q[1] would not compile.
int fromQueue = q.ElementAt(1);
int fromStack = s.ElementAt(1);

从语义的角度来看,它们都完全相同。 无论您使用哪种方法,都不会改变程序的含义。

你使用[]

  1. 如果可枚举被声明IList<T> ,因为:

    一个。 它更具可读性、惯用性和广泛理解。 我使用它的第一个原因。

    你想优化每一寸。 []应该稍微提高性能,因为它是直接查找并避免ElementAt请注意,如果IEnumerable<T>IList<T> ,则ElementAt使用索引器,因此它不是一个很大的增益)。

    C。 您仍处于 .NET 3.5 之前的时代。

您使用ElementAt

  1. 如果声明的类型仅仅是一个IEnumerable<T>

  2. 如果ElementAt使其更具可读性。 例如,在流畅的风格调用中:

     var x = GetThatList().ElementAt(1).Wash().Spin().Rinse().Dry(); //is more readable than var x = GetThatList()[1].Wash().Spin().Rinse().Dry();
  3. 如果您真的坚持对IList<T>IEnumerable<T>使用一致的样式。

简单来说,如果您将变量声明为IList<T>使用[] ,否则使用ElementAt我强调“声明”这一点,因为即使您的IEnumerable<T>是下面的IList<T> ,您也只能使用ElementAt . ElementAt转换为IList<T>以使用索引器是不行的(无论如何它都发生在ElementAt )。 可读性胜利。 如果您考虑到这一点,则选择起来很简单。 索引器样式是我在绝大多数情况下必须使用的样式。 我被迫很少使用ElementAt

[]上使用ElementAt()的唯一原因是您是否拥有或需要IEnumerable而不是IList

ElementAt()确实尝试首先转换为IList<T>然后使用索引器,因此使用ElementAt()性能可能与直接使用索引器没有显着差异。 因此,即使您有IList<T> ,如果将来有可能更改声明的类型,您也可能需要考虑ElementAt()

IEnumerable不支持直接索引访问,如果你知道你有一个列表,继续使用第二种形式,因为它更容易阅读(可以说)。

ElementAt()支持所有枚举,而不仅仅是列表——在性能方面它们在IList类型(即通用列表)上使用时是相同的,但如果您有一个使用索引访问的列表,则更具表现力。 如果您使用反射器查看ElementAt()的源代码,您将看到如果IEnumerable的类型为IList ,它将在内部使用索引访问:

..
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
    return list[index];
}
..

暂无
暂无

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

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