Return the most specific type, accept the most generic type



I guess most developers heard the guideline stating that, when designing methods, you should return the most specific type and accept the most generic one. Is it always applicable? Let’s try to look at it from different perspectives.

Non-collection types

Let’s start with some simple example. Let’s say we have the following class hierarchy:

public abstract class Person

{

}

 

public class Employee : Person

{

}

 

public class Customer : Person

{

}

And the following method:

public Employee GetMostValuablEmployee()

{

    /* Method body */

}

In this concrete example, it makes sense to return an Employee object. The Employee class might introduce some functionality specific to employees so it is a good idea to enable the client code to work with it. Changing the return value type to a more generic one puts too many restrictions to the method consumers. In the worst case, the client code would need to manually cast the Person object to an Employee, so it is better to not make them do that.

It is not always possible to return a value of a specific type (e.g. Employee or Customer). For example, if you have some polymorphic collection with objects of both types, you might have no choice other than returning an object of Person type:

public Person GetWhoEnteredOfficeLast()

{

    /* Method body */

}

That brings us to the first part of the guideline: make your methods return values of the most specific type possible.

Let’s look at another method:

public void SavePerson(Person person)

{

    /* Method body */

}

It’s a good decision to use the Person type for the input parameter instead of Employee or Customer because it enables the client code to use this method for operating a wider range of objects. It means that the client can pass this method customers and employees, and it will work for any of them.

Of course, it’s not always possible to use generic types for input parameters. Otherwise, we would always use Object as the type of the method parameters. That brings us to the second part of the guideline: make your methods accept parameters of the most generic types possible.

Okay, but why bother? Why do we need to follow these guidelines? It turns out, there’s some higher level reason behind these rules. That is, you should always try to make it easier for the clients of your code to use your API, even if the client is you. Good software design is all about simplicity. The simpler and easier you make it, the better.

Alright, the example with a person is rather obvious. But what about collections? Does this principle apply to them as well?

Collection types

You might have heard about another popular guideline stating that you should prefer interfaces over concrete types when dealing with collections. That means that rather than returning a List (or Dictionary, or HashSet) object:

public List<Employee> GetAllEmployees()

{

    /* Method body */

}

You need to introduce an IList interface:

public IList<Employee> GetAllEmployees()

{

    /* Method body */

}

Or even IEnumerable:

public IEnumerable<Employee> GetAllEmployees()

{

    /* Method body */

}

But List is a more specific type than IList and IEnumerable interfaces. Isn’t there a contradiction between these guidelines?

To answer this question, we need to step back and answer another one. Do we make it easier for our users to use the API if we introduce an interface rather than a concrete collection type for return values?

It turns out that it depends on two things:

  • Whether or not our software is a 3rd party library.
  • Whether or not the method we are talking about is a part of its public API.

If we build an in-house software to which source code we have full access, there’s no need to introduce an interface. In this case, we should follow the guideline that stands for returning objects of the most concrete type, which is the List class.

Some developers advocate we use interfaces in every situation where we need to return a collection. They argue that such behavior will pay-off later, when we decide to change the type of the collection.

Clearly, doing so breaks YAGNI principle in the case of an in-house development and leads to premature generalization. If you have full access to the code you develop, the effective price of changing it is close to zero. There’s no need to introduce an interface in advance because you can always do it later when it becomes clear that you have to.

On the other hand, the situation with 3rd party libraries is quite different. If you develop a redistributable library, your users might not be able to stand breaking changes you introduce, so you need to plan your API at least a little bit ahead of time. That means that you do need to anticipate some future changes in it and thus make steps towards some generalization even if it’s not required right now.

It means that you should always prefer interfaces over concrete collection types for methods that are parts of a redistributable library’s public API. This statement is true for both returning value and input parameter types.

Note that even in case of a redistributable library, the rule above is applicable only to the methods that comprise its public API. For private methods, it is still better to use a concrete collection type.

There’s another case where you would want to use interfaces even if you work with an in-house software. If a method returns a collection which your client code cannot change, it is a good design decision to convey this by using a read-only interface. But don’t use IEnumerable to represent a read-only collection. In most cases, IReadOnlyList or IReadOnlyCollection would be a better choice for that purpose.

Summary

  • If you develop an in-house software, do use the specific type for return values and the most generic type for input parameters even in case of collections.
  • If a method is a part of a redistributable library’s public API, use interfaces instead of concrete collection types to introduce both return values and input parameters.
  • If a method returns a read-only collection, show that by using IReadOnlyList or IReadOnlyCollection as the return value type.
Share