André Slupik
Posted on March 29, 2022
List<T>
is to collections what StringBuilder
is to strings. It's a mechanism for imperatively building a collection. List<T>
is designed for things like
IReadOnlyList<Element> GetElements(IReadOnlyCollection<Element> inputElement)
{
var list = new List<Element>();
foreach(var inputElem in inputElements)
{
if (...)
{
list.Add(inputElem);
}
}
return list;
}
Notice that List<T>
does not appear in the function signature, our API: it is an implementation detail, a mechanism. What the API says is give me any sort of finite collection, and I'll give you back another finite, indexed collection. The caller does not need to read the implementation to understand this.
Let's see what happens if we replace the input element with List<T>
.
IReadOnlyList<Element> GetElements(List<Element> inputElement)
This is now a confusing and restrictive function signature. It returns a collection, but it can also add, remove or change elements in the input collection. Does it? The caller doesn't know. He'd have to read the code, meaning that our API is no longer a clear abstraction. Furthermore the caller cannot pass in an Array
, an ImmutableArray
or any other collection, when these would in fact work perfectly fine for our purposes.
Don't take List<T>
as an input argument unless you mean to modify that input argument. In that case, your function should probably then return void
(or Task
, if it's async).
void AddElements(List<T> collection); // this does make sense
Now let's look at the other case, the return type. What would this mean?
List<Element> GetElements(IReadOnlyCollection<Element> inputElement)
This is not restrictive, but it's equally confusing. The function returns a collection which may then be modified by the caller. Why? Does the function store a reference to it somewhere so it can observe the changes later? Or worse, does it modify it while it's being used? Again, this is just strange API design that prompts me to read the implementation when I shouldn't have to.
I have a hard time coming up with a good reason to return List<T>
: if you have an internal mutable collection, surely it's a bad idea to share it with the outside world where it may be modified unexpectedly. Mutable state is best kept local.
Note that strictly speaking, returning IReadOnlyList<T>
does not guarantee immutability either, but it does send a strong message in that direction. Strictly speaking, the correct return type would be ImmutableArray<T>
or ImmutableList<T>
. I'm still on the fence; libraries seem to gravitate towards the IReadOnly
interfaces more than System.Collections.Immutable
, for whatever reason.
Posted on March 29, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 26, 2024