A while ago, I've been thinking on what would be the best approach to work with collections inside business entities.
As an example, I'll refer back to the domain classes I'm using in another post of me.
There, I have a class Order, and an OrderLine class. The Order class contains a collection of OrderLines.
In C# 2.0, this could look like this:
public class Order
{
private List<OrderLine> orderLines
= new List<OrderLine>();
}
The problem here, is that I want to have 'controlled access' to the
OrderLines collection in the
Order class.
What I mean is, that a consumer of these classes should not be able to add an
OrderLine to the
Order directly, since some additional action(s) need to be executed.
More precisely, if an
OrderLine is added to the
Order, I want to set a member of the
OrderLine, so that the
OrderLine knows to which
Order it belongs.
Therefore, I create an
AddOrderLine method in the
Order class:
public void AddOrderLine( OrderLine ol )
{
ol.OwningOrder = this;
this.orderLines.Add (ol);
}
A consumer of the code should always use the
AddOrderLine method if he wants to add an
OrderLine to the
Order.
You can enforce this easily by not making the
orderLines member public; easy enough.
However, at one time, the consumer of your code (or you :) ) will want to iterate through the
OrderLines of the
Order, or he will want to know how many
OrderLines an
Order contains.
Now, you'll have several options to achieve this:
One solution is to expose the orderLines collection to the public. In other words: create a public property which exposes the collection to the outside:
public class Order
{
private List<OrderLine> orderLines
= new List<OrderLine>();
public List<OrderLine> OrderLines
{
get
{
return orderLines;
}
}
}
Although this is a solution, I do not like it.
Now, we're unable to force the user to use the AddOrderLine method. Since the OrderLines collection is now publicly exposed, it is now possible to just use the Add method of the List to add OrderLines to the Order.
The documentation of the classes could of course mention that you should always use the AddOrderLine method, but there's no real hard constraint here. (If the classes are used inappropriatly, the program could of course crash, so then there is a constraint after all. ;) However, then the programmer using those classes will maybe lose a lot of time to detect the error he made.
Another solution is to keep the OrderLines collection private, and to add some extra members to the Order class. In this way, uncontrolled access to the OrderLines is not possible.
However, there are offcourse disadvantages to this approach as well. First of all, you'll have to write some tedious code that just delegates the functionality to the Collection class, like this:
public class Order
{
private List<OrderLine> orderLines = ...
public void AddOrderLine( OrderLine ol )
{
ol.OwningOrder = this;
orderLines.Add (ol);
}
public int NumberOfOrderLines
{
get
{
return orderLines.Count;
}
}
}
Unnecessary to say that this is just a boring task.
Then, to be able to iterate through the
OrderLines of an
Order, you could write code like this:
public class Order
{
private List<OrderLine> orderLines = ...
...
public OrderLine[] GetOrderLines()
{
return orderLines.ToArray();
}
}
Actually, I think this is plain ugly.
I always have to make a choice between these 2 approaches, where each one has his disadvantages. After doing this too much, I wanted a better solution for this problem, and after some reading and experimenting, I finally found one. It is in fact such a simple solution that I can't imagine why I haven't been using this one much earlier...
It's just nothing more then this:
Keep the collection private, create the necessary methods to provide the necessary controlled access to the collection (for instance the AddOrderLine method, and create a public property which returns a read-only instance of the collection, so that you can iterate through the collection, change existing instances of objects within the collection (at least, this is only true if you have reference types in the collection; you will not be allowed to modify value types on a ReadOnlyCollection, get the number of objects that are in the collection, ...
In code, it looks like this:
class Order
{
private List<OrderLine> orderLines =
new List<OrderLine>();
public void AddOrderLine( OrderLine ol )
{
ol.Order = this;
orderLines.Add (ol);
}
public ReadOnlyCollection<OrderLine> OrderLines
{
get
{
return orderLines.AsReadOnly();
}
}
}
This is the C# 2.0 version.
Now, it is impossible to add OrderLines in an uncontrolled way, but it is possible to iterate the
OrderLines that are in the collection, and modify existing
OrderLine objects that are already in the collection (since it are instances of a reference type; if
OrderLine was a struct, it would not be possible to modify them.
In C# 1.x, it looks like this:
class Order
{
private IList orderLines = new ArrayList();
public void AddOrderLine( OrderLine ol )
{
ol.Order = this;
orderLines.Add (ol);
}
public IList OrderLines
{
get
{
return ArrayList.ReadOnly (orderLines);
}
}
}
Here, I return a readonly copy of the ArrayList. It is still possible to iterate and change the items that are in the collection, but it is impossible to Add an
OrderLine to the
Order in an uncontrolled fashion like this:
Order o = new Order();
OrderLine ol = new OrderLine();
o.OrderLines.Add (ol);
In this case, a
System.NotSupportedException will be thrown.
It would of course be nicer to not have an Add and Remove method on the read-only ArrayList property, like we've achieved in the C# 2.0 version (the
ReadOnlyCollection class does not have Add and Remove methods).
In .NET 1.x we can achieve that by letting the property return an
ICollection instead of an
IList, however, then we're not able to use an indexer to retrieve an
OrderLine like this:
OrderLine ol = theOrder.OrderLines[i] as OrderLine;
Therefore, I prefer to return an
IList instead of an
ICollection