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

EJA*_*EJA 54 c#

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

在这两种情况下,如果您未能检查集合的范围,您将获得"超出范围"的异常.这只是编码风格偏好吗?

如果有人需要一个例子:

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

byte testByte = myList[2];
Run Code Online (Sandbox Code Playgroud)

Mas*_*sif 28

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

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

  • `ElementAt()`对于`IList`来说很好,如果你查看它的代码,它会转换为IList,如果成功则使用`[]`.这通常适用于所有LINQ扩展方法. (28认同)

Mat*_*eer 19

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

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

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

还有一些ElementAtOrDefault()通常非常方便.


Mik*_*Vee 11

在某些情况下使用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);
Run Code Online (Sandbox Code Playgroud)

导致此记录输出:

...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
Run Code Online (Sandbox Code Playgroud)

一旦我创建了一个中间数组来存储SharedStringItem用于引用,我的时间就是100秒到10秒,现在在相同的时间内处理每一行.

这行代码:

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

并循环使用以下代码:

SharedStringItem ssi = ssia[ssi_index];
Run Code Online (Sandbox Code Playgroud)

导致此记录输出:

...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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,转换为数组只需要一小段时间就可以获得33,560个项目,并且非常值得加快我的导入过程.


Dan*_*Tao 8

更新:我觉得这是一个有趣的话题,并决定发表关于它的博客.

基本上,我的观点是,在没有要求的情况下利用抽象进行随机访问很少有意义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);
Run Code Online (Sandbox Code Playgroud)

  • @Matt:可能是,但我仍然不会将它用于此类型.这是毫无意义的:索引器在那里; 为什么不直接使用它?此外,LINQ扩展方法中的优化通常是有争议的,并且在大多数情况下没有记录,这意味着BCL团队可以改变他们的想法并将其取出. (3认同)
  • 他们有记录.从MSDN:"如果源的类型实现IList <T>,则该实现用于获取指定索引处的元素".我一般发现LINQ已经使C#代码具有更高的水平,我几乎从不关心(甚至不知道)底层类型,我只是在枚举上执行操作.但那只是我的经历. (3认同)
  • @MattGreer和Dan Tao,很容易只使用`ElementAt()`因为它允许你的代码更加一致,并且无论集合的类型如何,你的代码都会继续工作.但是,如果元素抓取发生在一个循环中,你会看到O(n)的"IList <T>"与O(n ^ 2)否则.所以,让我们说在你进入循环之前,你明确地转换为`List <T>`.在*this*case中,我认为使用`[]`而不是`ElementAt()`因为它传达了查找保证快速. (3认同)
  • 如果底层类型实现`IList`,它将使用`[]`.LINQ通常总是试图从底层类型中找到有效的快捷方式. (2认同)
  • 问候Dan,您的博客链接似乎已被破坏,您是否可以提供更新的链接? (2认同)

Ioa*_*mas 6

使用ElementAt()过的唯一原因[]是,如果你有或需要一个IEnumerable而不是一个IList.

  • +1.``[``在我看来比'ElementAt'更具可读性. (6认同)

Bro*_*ass 5

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

ElementAt()支持所有枚举,而不仅仅是列表 - 从性能角度来看,它们在用于某种类型IList(即通用列表)时是相同的,但如果您有一个使用索引访问的列表,则更具表现力。如果您查看 with Reflector 的源代码,您会发现如果类型为 ,ElementAt()它将在内部使用索引访问:IEnumerableIList

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


Joe*_*nos 5

ElementAt()尝试IList<T>首先进行转换然后使用索引器,因此性能ElementAt()可能与直接使用索引器没有显着差异.所以即使你有一个IList<T>,你也可以考虑一下ElementAt(),如果你将来有可能改变你声明的类型.


naw*_*fal 5

从语义的角度来看,它们都完全相同.无论你使用哪个都不会改变程序的含义.

你用[]:

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

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

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

    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();
    
    Run Code Online (Sandbox Code Playgroud)
  3. 如果你真的坚持使用一致的风格IList<T>IEnumerable<T>.

简单来说,如果你把变量声明为IList<T>use [],否则使用ElementAt(我强调点"声明",因为即使你IEnumerable<T>是一个IList<T>底层,你也只能使用ElementAt.转换IList<T>为使用索引器是不是没有(它发生在里面)ElementAt无论如何)).可读性取得了成功.如果你有这个想法,它很容易选择.在绝大多数情况下,我必须使用索引器样式.我ElementAt很少被迫使用.