I’m an ardent proponent of using Arrays (
T) over Lists (
Lisf<T>) in C#. In my case, it’s mostly web services running in Azure and libraries, consumed by such services. In this blog post I’ll be collecting the pros and cons of using one over another.
First and foremost, the methodological argument. Types matter, whenever it’s a parameter type or a return type. There are many types but this one is mine. Type indicates the intend.
What’s the intend of a list? A list is a collection that allows an item to be added:
What means a list is not a final collection. Its “true name” is
ArrayList. That it, the
IList interface implemented by using an
Array. Historically, C# design has copied so many things from Java but unfortunately this class name one was not one of them. Its core functionality pivots around automatic resizing (you can arbitrarily add and remove items) and capacity.
While an array is the opposite. It’s final. Once created, its size cannot be changed. Of course, you can resize it but that will be a new array with the content copied over. Like strings, arrays are immutable in that regards.
Side note: there are already readonly lists and collections, as well immutable arrays. There is a number of problems with them:
- Fom a practical standpoint of the cost of writing and maintaining the code. In general, programmers are too lazy and the name is too long. How do you like
Task<IReadOnlyCollection<OrderDetails>> comparing to just
Task<OrderDetails>? Because C# is notoriously “chatty” aka high ceremony language.
- From a technical standpoint of writing a better, more efficient code at the baseline, means from the beginning. Both types
List<T> are optimized for enumeration and return a struct from
GetEnumerator(), if called on the concrete class. As soon as it’s called on the interface (IEnumerable<T> or any of its descendants) the struct is boxed and all the benefits disappear.
As I mentioned above, I’m using C# to write web services in the cloud. What means it’s strings from a database sent over a network. The result of a query? Final. The results in a HTTP request? Final as well.
Then why would your Database-over-Network class return a type whose intend is to add stuff into it?
Obviously, it should be an array instead.
Here we could stop. In my mind, this argument alone is enough to always use arras until you clearly need otherwise. But let’s discuss other, technical arguments supporting my position.
- What happens when you’re instantiating a generic
List<T>? “The whole transitive closure of types, starting with that root type, will be compiled”. See Joe Duffy’s blog post on this topic. Generics are not bad but they’re relatively expensive.
- Accessing an item inside an array involves a bounds checking. Which are in many cases eliminated by JIT optimization down the road anyway. See this question on Stack Overflow and Matt Warren’s blog post.The code for array indexer is getting translated into IL directly:
While list indexer is a whole virtual method call:
IL_001b: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<int32>::get_Item(int32)
Task<T> is not covariant. What means
To be continued….