Let me start from a blog post that Krzysztof Cwalina—a prominent member of the .NET Framework team—made some time ago. You find it at http://blogs.gotdotnet.com/kcwalina/archive/2005/09/26/474010.aspx. In the post, Krzysztof doesn’t recommend using List<T> in the signature of public members of custom classes. I’ll get to the details of the post in a moment. Before going any further, though, I want to introduce idiomatic design.
When it comes to designing software, general principles of object-oriented design as well as basic and fundamental principles such as low coupling and high cohesion are always valid and should always be taken into due consideration.
However, when you step inside the design, at some point you meet the technology. When this happens, you may need to review the way you apply principles and aim at them in the context of the specific technology or platform you’re using. This is called idiomatic design. Simply put, it is design when you conceptually envision a class and its attribute; it is idiomatic design when you know you will actually be using the .NET Framework (or any other framework) and adapt your decisions to the context.
As far as the .NET Framework is concerned, a set of idiomatic design rules exists under the name of Framework Design Guidelines. You can access them online from the following URL: http://msdn.microsoft.com/en-us/library/ms229042.aspx. The Framework Design Guidelines also exists as a book written by Microsoft’s Krzysztof Cwalina and Brad Abrams.
The advice of not using List<T> in a public API is definitely an example of an idiomatic design rule. Let’s briefly recap why List<T> is not recommended in a public signature and from there I’ll take on the more general theme of code reusability.
Don’t Use List in a Public API
According to Cwalina, two are the reasons for not using List<T> publicly. One is that List<T> is a rather bloated type with many members not relevant in many scenarios. The other reason is that the class is unsealed, but not specifically designed to be extended. Let’s have a look at the class signature:
The class is designed to be a strongly typed list of objects that can be accessed by index. The class also provides methods to search, sort, and manipulate lists. If you look at the methods on the implemented interfaces, you obtain the following list of members, subdivided by interface:
In addition, the class List<T> counts a similar set of methods but non-generic. If you look at the actual list of members, you find many more methods: BinarySearch, Reverse, Sort, a variety of Find methods, and a bunch of predicate-based methods including ForEach and TrueForAll.
In a nutshell, List<T> is not merely a class that implements the IList<T> interface. It’s, instead, quite a large object. Hence, it is preferable that you use simpler and thinner lists in your public APIs.
Don’t Use Certain Classes in a Public API
Behind the aforementioned idiomatic design rule, a couple of general principles of (object-oriented) software design can be recognized. One is the Single Responsibility Principle (SRP).
SRP states that each class should have only one reason to change. In other words, a class should expose a cohesive and anyway limited set of functionality. Bloated classes with methods that range from search to I/O, from manipulation to index-based access are hard to classify as SRP-safe classes.
Breaking the SRP principle is not per se a bad statement and doesn’t affect in any way the working of the code based on such a class. It is simply a sign that confirms you have a lot of room left for improving the design of the class.
Using the List<T> class internally is absolutely fine and, in fact, the class is really helpful and performs optimally. When it comes to interfacing other layers and classes, though, a bit of attention is required. Full respect of the SRP principle ensures maintainability and readability of the code.
Let’s move to the second motivation for not using List<T> in public signatures—the class is further inheritable, but it is not designed for being extended.
In .NET coding, leaving a class unsealed (which is the default setting indeed) is, well, a responsibility you take on your own. If you leave a class unsealed around your code you expose yourself to the possibility that others will inherit from that. What if at some point you have to trace back and seal a previously unsealed class that was inherited? In software development, changes of requirements are the rule, not the exception. For this reason, it would be preferable to seal as many classes as possible unless a strong reason exists to further inherit.
Using unsealed classes in public signatures may expose your code to a subtle issue that is well summarized by the Liskov Substitution Principle (LSP).
The Liskov Principle
When a new class is derived from an existing one, the derived class can be used in any place where the parent class is accepted. This is just polymorphism, isn’t it? Well, the LSP principle just recalls that polymorphism is a great feature, but it doesn’t come for free by simply inheriting a class. The principle says:
Subclasses should be substitutable for their base classes.
Apparently, you get this free of charge from just using an object-oriented language. It is not always as simple as you may think, though.
If the base class has virtual methods, and through virtual methods you make access to internal, hidden members, what would happen to overrides? Are these methods guaranteed to verify the same preconditions of overridden methods? If a virtual method accesses a private member, there’s no possibility for an override to fulfill the same precondition—the override can’t just access the private member which would make it unfit as an override. As a result, the override may alter the expected behavior of the base class thus potentially breaking polymorphism.
Now does this mean that the List<T> is not LSP-safe? I wouldn’t say so, but for sure the class is not designed with inheritance in mind. From a coding perspective, using List<T> doesn’t enable a great deal of polymorphism, but doesn’t break (not even potentially) any code if used. The two points made by Cwalina are, of course, tightly related: the class is per se bloated and there’s no clear polymorphism benefit in using List<T> in public APIs. In other words, you can; but you shouldn’t.
Let’s see why List<T> is still LSP-safe, but not really beneficial for further inheritance.
The first key point is that the class has no virtual methods. And virtual methods are just the means through which inheritance delivers benefits—by overriding methods you can alter the behavior of the class and bend it to your needs. Having no virtual methods in derivable class reduces the risk of breaking LSP, but at the cost of not making inheritance attractive and beneficial. List<T> has no protected members either. This means that when you derive a class from it all that you can do is adding new methods. New methods, however, have no privileged access to internal members, because when not public they are private. You can’t really specialize the class behavior; you can only extend it in way that resembles composition rather than inheritance.
When you design a class for inheritance, you make available some virtual methods, either public or protected. These methods should be LSP-safe meaning that they only access protected or public members of the class and never private members. Access to private members can’t be replicated by overrides which will make base and derived classes not semantically equivalent from the perspective of a caller. This is key point to keep in mind when considering inheritance in an object-oriented world.
What to Do Then?
In his aforementioned post and book, Cwalina recommends that you avoid List<T> in public APIs. What is the alternative? You should use a simpler list-based type such as Collection<T> or ReadOnlyCollection<T> for outputs and class properties. You should go for IEnumerable<T>, ICollection<T>, IList<T> for input parameters.
Are these guidelines valid in general and beyond the specific case of the List<T> class? The SRP principle comes handy again. Each class you design should take care of the smallest possible set of responsibilities. If you need to pass in a list of objects, then you should start with IEnumerable and increase the amount of responsibilities only when necessary.
When you return an object from a method, you should return the simplest possible object that works in the scenario. In the unfortunate case that no predefined classes match your needs, you should consider creating a custom class that implements a given set of interfaces. You should try to program to interfaces as much as possible. After all, “Program to an interface, not to an implementation” is the first principle of object-oriented design.
Dino Esposito is one of the world's authorities on Web technology and software architecture. Dino published an array of books, most of which are considered state-of-the-art in their respective areas. His most recent books are “Microsoft ® .NET: Architecting Applications for the Enterprise” and “...
This author has published 54 articles on DotNetSlackers. View other articles or the complete profile here.
Please login to rate or to leave a comment.