Reputation: 12438
I've recently read something about using interfaces when exposing collections instead of concrete implementations (IEnumerable instead of List). I'm trying to do that now in my code. However, when I expose a property that return IEnumerable, I'm having some difficulty of not allowing nulls as a return value. Example:
public class HumanResource
{
public IEnumerable<EmployeeModel> Employees
{
get
{
// return what?
}
}
}
What should I return in the getter? I don't want to use automatic properties for this as I want to avoid nulls. What I want is to return a new collection with no items. Of course I can return any type that implements IEnumerable but how will the external user of the class know that? Or did I understand this exposing interface instead of concrete implementations wrong?
EDIT: Removed setter
Upvotes: 2
Views: 993
Reputation: 107277
You still need to provide an internal backing collection for the property in your class. You can initialize the collection in the constructor, or in the field declaration:
public class HumanResource
{
private readonly IList<EmployeeModel> _employees = new List<EmployeeModel>();
public IEnumerable<EmployeeModel> Employees
{
get
{
return _employees;
}
// No Setter - callers may only enumerate the collection
}
}
As an aside, note that even if you did use an automatic property (e.g. List<EmployeeModel>
), that it would assume a default value of null, unless otherwise initialized elsewhere, so nothing changes in this respect.
Edit, Re : What are the benefits?
HumanResource
List<>
to an IEnumerable<>
, it means the caller can only do read-only actions on the internal collection, such as to iterate it. In addition, IEnumerable<>
can be used in a lazy iteration, allowing the caller to quit enumerating as soon as it has the data it needs.Array
, then LINQ extension methods such as .ToArray()
, .ToList()
, .ToDictionary()
can be used. Doing so will create new collections for the caller, but with references to the same EmployeeModel
objects. The performance penalties of doing this are minimal.One final note is that there is usually no point in making the setter on an IEnumerable
property private, or declaring the backing field as an IEnumerable
, as this will prevent the class itself from using impure methods to manipulate the collection (i.e. add or remove objects from it), as doing so would require a cast, e.g.:
public class HumanResource
{
public IEnumerable<EmployeeModel> Employees
{
get;
private set;
}
public HumanResource()
{
// Although the property can be assigned in the CTor to prevent the null issue ...
Employees = new List<EmployeeModel>();
}
private void AddProductiveEmployee()
{
// ... We need to continually cast within the class, which is just silly.
(Employees as IList).Add(new EmployeeModel());
}
We would have the same problem with the manual backing field approach with an internal IEnumerable<>
// Unlikely to be useful
private readonly IEnumerable<EmployeeModel> _employees = new List<EmployeeModel>();
TL;DR
Upvotes: 0
Reputation: 151634
Of course I can return any type that implements IEnumerable but how will the external user of the class know that?
They don't have to know that, that's exactly the point.
Your property promises to return an IEnumerable<EmplyeeModel>
, and that's exactly what happens. It doesn't matter which class implementing this interface your code returns.
What I want is to return a new collection with no items.
So, Enumerable.Empty<EmplyeeModel>()
or new List<EmployeeModel>()
will do just fine.
When designing an API you need to think about what the consumers will do with the data types you return, and decide upon that accordingly.
Usually an IEnumerable<T>
for collections suits everyone. When they want it in a list, they can do new List<T>(yourEnumerable)
, or yourEnumerable.ToArray()
to use it as an array.
Upvotes: 5
Reputation: 726779
What I want is to return a new collection with no items.
Properties let you do that very easily:
public class HumanResource
{
// This is the real employees that gets returned when its not null
private IEnumerable<EmployeeModel> employees; // may be null
// This is the empty IEnumerable that gets returned when employees is null
private static readonly IEnumerable<EmployeeModel> EmptyEmployees =
new EmployeeModel[0];
public IEnumerable<EmployeeModel> Employees
{
get
{
return employees ?? EmptyEmployees;
}
set {};
}
}
The code returns an empty array when employees
variable is set to null
. You can set employees
to a collection of any type that implements IEnumerable<EmployeeModel>
, or even to an array if you prefer. This is possible because you return by interface.
The flip side of this, of course, is that the clients would have no direct access to methods of properties that are not exposed through the interface. For example, if employees
is actually a List
, the callers would have to use LINQ's Count()
instead of obtaining .Count
directly. Of course you can expose a different interface, say, IList<EmployeeModel>
, to let your clients use additional methods.
Upvotes: 1