Many people believe that you should be using List<T> to return collections from a public API. However, there are several reasons not to do so; instead return a ReadOnlyCollection<T>. The are several benefits of
using a ReadOnlyCollection<T>:
- The *collection itself* cannot be modified – not the items within it. i.e. you
cannot add, remove, or replace any item in the collection. So, it keeps the
collection itself intact – not the values of the items within it. To protect
the items in it, you’d probably have to have private “setters” on any properties
for the reference object in the collection – which is generally recommended.
However, items that are declared as [DataMember] must have both public getters
and setters. So if you are in that case, you can’t do the private setter, but
you can still use the ReadOnlyCollection<T>. Using this as a Design
paradigm can prevent subtle bugs from popping up.
- Performance: The List<T> class is essentially a wrapper around a strongly-typed array. Since arrays are immutable (cannot be re-sized), any modifications made to a List<T> must create a complete copy of List<T> and add the new item. The initial capacity for a List<T> is ‘4’ unless specified thru the overloaded constructor. Obviously this can be very bad for memory and performance. Don’t forget that not only the items directly within the List<T> are copied – but every value and object with the entire object graph for that reference type – this could easily contain other references type, other collections, etc. This is why it is best practice to create/initialize a List<T> instance with the overloaded constructor which takes an ‘int’ denoting the size of the List to be created. This can almost always be done since you are usually iterating on a “known” size value at runtime. For example, creating a List<T> of objects from a "Repository or DataService" method usually iterates/reads from a IDataReader object which has a property of ‘RecordsAffected’. If you are going to be putting an item in the List<T> based on the number of times the IDataReader is Read – while(reader.Read()) you can easily create the list like so (and it is preferable to do it like this):
if (reader != null && reader.RecordsAffected > 0)
{
List<Foo> someList = new List<Foo>(reader.RecordsAffected);
while (reader.Read())
{
someList.Add(...);
...
}
}
Just as a side note…every time
that a List<T> needs to grow in size, the size is *
doubled*. So,
if you happen to add just one more item to a List<T> that wasn’t
constructed with a pre-determined size, and the List<T> expands to
accommodate the new item, there will be a whole lot of unused space at the end
of the List<T> even though there aren’t any items in it – bad for memory
and performance.