Tue, Dec 15 2009 11:40
VB 9 or later has a particular feature that can make life easier in some circumstances: the compiler allows you to access indexed items on non indexed enumerables. Let’s say you have code such as:
Dim items as IEnumerable(Of String)
. . . .
For i = 0 to items.Count - 1
VB will compile that for you. The Count is an extension method that loops through the list to determine the count; luckily VB calls that only once. The real killer though is items(i) is actually compiled as items.ElementAtOrDefault(i).
The ElementAtOrDefault extension tries to cast the IEnumerable(Of T) to an IList(Of T); if the IEnumerable isn’t actually an IList, it loops through the collection, returning at the given index. As you can imagine this would get extremely slow for large lists especially inside a loop.
If your IEnumerable is in fact an IList, such as a List(Of T), then the compiler magically adding this extension call makes life a little easier as you don’t have to cast to IList yourself.
You have to be really careful when doing any code maintenance, especially if you change an IEnumerable source to a different collection base that doesn’t implement IList(Of T). You can end up writing really bad, slow code really easily.
I got bitten by the bad, but I caught it immediately as I was curious about the collection base I was using. In my particular case I had swapped an List(Of T) out with a ConcurrentBag(Of T). ConcurrentBag(Of T) basically uses linked lists where each item is stored as a node. Each node has a previous and next node; the list has a head and a tail. I thought it strange that ConcurrentBag would have an indexer given that would force walking through the nodes. I went to change the items(i) to items.Item(i) and BINGO it wouldn’t compile.
Possible best practices to avoid being bitten by this:
(1) Use For Each loops, not indexers. Your code will be far more flexible/manageable
(2) If you only want the first item, make it explicit by using First or FirstOrDefault extensions.
(3) Try to do the explicit cast to IList(Of T) yourself and work with an IList(Of T) instead of an IEnumerable(Of T)
(4) Consider explicitly using the Item property , eg: items.Item(0) instead of items(0)
I’m not really comfortable with having to use the Item property: hopefully practices 1 to 3 will alleviate the need for practice 4.