The benefits of using Arrays over Lists in C#

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:

List<T>.Add(T)

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 T[] and 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?

interface IOrderService
{
    Task<List<OrderDetails>> GetOrders();
}

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.

  1. 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.
  1. 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:
IL_0014: ldelem.i4

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)
  1. Task<T> is not covariant. What means

To be continued….

This entry was posted in Programming and tagged . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.