Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why IReadOnlyCollection has ElementAt but not IndexOf

Tags:

I am working with a IReadOnlyCollection of objects.

Now I'm a bit surprised, because I can use linq extension method ElementAt(). But I don't have access to IndexOf().

This to me looks a bit illogical: I can get the element at a given position, but I cannot get the position of that very same element.

Is there a specific reason for it?

I've already read -> How to get the index of an element in an IEnumerable? and I'm not totally happy with the response.

like image 964
Lorenzo Santoro Avatar asked May 25 '16 08:05

Lorenzo Santoro


2 Answers

IReadOnlyCollection is a collection, not a list, so strictly speaking, it should not even have ElementAt(). This method is defined in IEnumerable as a convenience, and IReadOnlyCollection has it because it inherits it from IEnumerable. If you look at the source code, it checks whether the IEnumerable is in fact an IList, and if so it returns the element at the requested index, otherwise it proceeds to do a linear traversal of the IEnumerable until the requested index, which is inefficient.

So, you might ask why IEnumerable has an ElementAt() but not IndexOf(), but I do not find this question very interesting, because it should not have either of these methods. An IEnumerable is not supposed to be indexable.

Now, a very interesting question is why IReadOnlyList has no IndexOf() either.

IReadOnlyList<T> has no IndexOf() for no good reason whatsoever.

If you really want to find a reason to mention, then the reason is historical:

Back in the mid-nineties when C# was laid down, people had not quite started to realize the benefits of immutability and readonlyness, so the IList<T> interface that they baked into the language was, unfortunately, mutable.

The right thing would have been to come up with IReadOnlyList<T> as the base interface, and make IList<T> extend it, adding mutation methods only, but that's not what happened.

IReadOnlyList<T> was invented a considerable time after IList<T>, and by that time it was too late to redefine IList<T> and make it extend IReadOnlyList<T>. So, IReadOnlyList<T> was built from scratch.

They could not make IReadOnlyList<T> extend IList<T>, because then it would have inherited the mutation methods, so they based it on IReadOnlyCollection<T> and IEnumerable<T> instead. They added the this[i] indexer, but then they either forgot to add other methods like IndexOf(), or they intentionally omitted them since they can be implemented as extension methods, thus keeping the interface simpler. But they did not provide any such extension methods.

So, here, is an extension method that adds IndexOf() to IReadOnlyList<T>:

using Collections = System.Collections.Generic;      public static int IndexOf<T>( this Collections.IReadOnlyList<T> self, T elementToFind )     {         int i = 0;         foreach( T element in self )         {             if( Equals( element, elementToFind ) )                 return i;             i++;         }         return -1;     } 

Be aware of the fact that this extension method is not as powerful as a method built into the interface would be. For example, if you are implementing a collection which expects an IEqualityComparer<T> as a construction (or otherwise separate) parameter, this extension method will be blissfully unaware of it, and this will of course lead to bugs. (Thanks to Grx70 for pointing this out in the comments.)

like image 144
Mike Nakis Avatar answered Nov 15 '22 11:11

Mike Nakis


It is because the IReadOnlyCollection (which implements IEnumerable) does not necessarily implement indexing, which often required when you want to numerically order a List. IndexOf is from IList.

Think of a collection without index like Dictionary for example, there is no concept of numeric index in Dictionary. In Dictionary, the order is not guaranteed, only one to one relation between key and value. Thus, collection does not necessarily imply numeric indexing.

Another reason is because IEnumerable is not really two ways traffic. Think of it this way: IEnumerable may enumerate the items x times as you specify and find the element at x (that is, ElementAt), but it cannot efficiently know if any of its element is located in which index (that is, IndexOf).

But yes, it is still pretty weird even you think it this way as would expect it to have either both ElementAt and IndexOf or none.

like image 35
Ian Avatar answered Nov 15 '22 11:11

Ian