This seems like a silly question, but I haven't found the answer, so here it is. :)
In both cases, you will get an "out-of-range" exception if you fail to check the bounds of your collection. Is this just coding style preference?
And in case someone needs an example:
List开发者_如何学运维<byte> myList = new List<byte>(){0x01, 0x02, 0x03};
byte testByte = myList.ElementAt(2);
versus
byte testByte = myList[2];
Because Enumerable
is more generic, and a collection represented by enumerable may not have an indexer.
But, if it does - don't use ElementAt()
it's probably not going to be as efficient.
ElementAt()
provides a unified interface for all enumerations in C#. I tend to use it quite often as I like the common API.
If the underlying type supports random access (ie, it supports the []
operator), then ElementAt will make use of that. So the only overhead is an extra method call (which is almost never relevant).
If the underlying type does not support random access, then ElementAt()
simulates it by iterating the enumeration until it arrives at the element you care about. This can be very expensive and even have side effects sometimes.
There is also ElementAtOrDefault()
which is often very handy.
AVOID using ElementAt() in certain scenarios!!!
If you know you're going to look up each element, and you have (or could have) over 500, then just call ToArray(), store it in a reusable array variable, and index it off that way.
For example; my code was reading data from of an Excel file.
I was using ElementAt() to find the SharedStringItem my Cell was referencing.
With 500 or less lines, you probably won't notice the difference.
With 16K lines, it was taking 100 seconds.
To make things worse (with each row it read) the bigger the index grew and the more it had to index through on each iteration, so it took longer than it should have.
The first 1,000 rows took 2.5 Seconds, while the last 1,000 rows took 10.7 Seconds.
Looping through this line of code:
SharedStringItem ssi = sst.Elements<SharedStringItem>().ElementAt(ssi_index);
Resulted in this Logged Output:
...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
Once I created an intermediary Array to store the SharedStringItem's in for referencing, my time went 100 Seconds down to 10 Seconds and now handles each row in the same amount of time.
This line of code:
SharedStringItem[] ssia = sst == null ? null : sst.Elements<SharedStringItem>().ToArray();
Console.WriteLine("ToArray():" + watch.Elapsed.TotalSeconds + " Len:" + ssia.LongCount());
and Looping through this line of code:
SharedStringItem ssi = ssia[ssi_index];
Resulted in this Logged Output:
...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
As you can see, converting to an Array took a fraction of a second for 33,560 items and was well worth it to speed up my import process.
Update: I felt this was an interesting topic and decided to blog about it.
Basically, my opinion is that it rarely makes sense to utilize an abstraction for random access without requiring IList<T>
. If you are writing code that wants to access the elements of a collection randomly by index, then just require one that gives you that. And if you're requiring an IList<T>
to begin with, I really see no point in using ElementAt
instead of the indexer.
But that's just my opinion, of course.
Don't use it for IList<T>
implementations like T[]
or List<T>
. Only use it if you need to for collection types which don't provide random access, such as Queue<T>
or 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);
The only reason to use ElementAt()
over []
, is if you have or need an IEnumerable
instead of an IList
.
From a semantic standpoint, they both do exactly the same. Whichever you use its not going to change the meaning of the program.
You use []
:
if the enumerable is declared as an
IList<T>
, because:a. it's more readable, idiomatic, and widely understood. No.1 reason for me to use it.
b. you want to optimize every inch.
[]
should be marginally more performant since it's a direct look up and avoids a cast (note thatElementAt
uses the indexer if theIEnumerable<T>
is anIList<T>
, hence its not a big gain).c. you're still in pre .NET 3.5 era.
You use ElementAt
:
if the declared type is merely an
IEnumerable<T>
.if
ElementAt
makes it more readable. For eg, in fluent style calls:var x = GetThatList().ElementAt(1).Wash().Spin().Rinse().Dry(); //is more readable than var x = GetThatList()[1].Wash().Spin().Rinse().Dry();
if you are really adamant on using a consistent style for both
IList<T>
andIEnumerable<T>
.
In simple terms, if you have the variable declared as IList<T>
use []
, otherwise use ElementAt
(I stress the point "declared" because even if your IEnumerable<T>
is an IList<T>
underneath, you can only use ElementAt
. Casting to IList<T>
to use the indexer is a no no (it happens inside ElementAt
anyway)). Readability triumphs. If you have that in mind its simple to choose. Indexer style is what I have to use in vast majority of cases. I'm forced to use ElementAt
very rarely.
ElementAt()
does attempt to cast as IList<T>
first then use the indexer, so performance with ElementAt()
probably won't be significantly different than using the indexer directly. So even if you have an IList<T>
, you may want to consider ElementAt()
, if there's ever a possibility that you'd change your declared type in the future.
IEnumerable
doesn't support direct index access, if you know you have a list, keep using the second form, since it is much easier to read (arguably).
ElementAt()
is supported for all enumerations, not just lists - performance wise they are the same when used on a type of IList
(i.e. a generic List), but if you have a list using the index access is more expressive. If you look at the source of ElementAt()
with reflector, you will see that it will internally use the index access if the IEnumerable
is of type IList
:
..
IList<TSource> list = source as IList<TSource>;
if (list != null)
{
return list[index];
}
..
精彩评论