Do not expose List<T> in object models. Use Collection<T>, ReadOnlyCollection<T> or KeyedCollection
Reason:
· List<T> is not designed for extensibility. It does not support members that can be overridden. An object returning a List<T> won’t be able to get notified when the collection is changed.
· List<T> is intended to be a class used in internal implementation. By exposing a List<T> in the interface of your class, you are breaking encapsulation by giving client code access to an implementation detail of your class--especially if your property provides direct access to the internal List<T> .
· Collection<T> is designed for extensibility and helps you to notify changes in the collection by overriding the methods exposed by the Collection<T>
David Kean from MS Code Analysis team has a very good article explaining the rule. I will try to use Dave’s example to explain the advantages of using Collection<T> over List<T> while exposing the type for public API.
The following example shows how to derive a collection class from a constructed type of Collection<T> generic class, and how to override the InsertItem, ClearItem, SetItem and RemoveItem to provide notification about the collection changes.
public class Person
{
private AddressCollection _addresses;
public Collection<Address> Addresses
{
get
{
if (_addresses == null)
_addresses = new AddressCollection(this);
return _addresses;
}
}
public event EventHandler<addresschangedeventargs> AddressChanged;
protected virtual void OnAddressInserted(Address item)
{
EventHandler<addresschangedeventargs> temp = AddressChanged;
if (temp != null)
temp(this, new AddressChangedEventArgs(ChangeType.Added, item.Country, null));
}
private void RaiseAddressInserted(Address item)
{
OnAddressInserted(item);
}
private class AddressCollection : Collection<address>
{
private Person _owner;
public AddressCollection(Person _person)
{
_owner = _person;
}
protected override void InsertItem(int index, Address item)
{
base.InsertItem(index, item);
_owner.RaiseAddressInserted(item);
}
}
}
From the above code you can see how to get notified by exposing the Collection<T> when changes occur in the collection. Similarly you can implement the RemoveItem, ClearItem and SetItem methods and raise events that notify the respective changes.
You can also encapsulate the List<T> and expose the methods that are relevant to the application by calling the underlying List<T> implementation. I will show how to expose the Find(), Sort() and FindAll() methods of List<T> while using Collection<T>
For example I have made some changes to the AddressCollection class for exposing the List<T> methods
private class AddressCollection : Collection<Address>
{
private Person _owner;
public AddressCollection(Person _person) : base(new List<Address>())
{
_owner = _person;
}
. . . .
. . . .
protected internal Address Find(Predicate<Address> match)
{
List<Address> items = (List<Address> )Items;
return items.Find(match);
}
protected internal void Sort(Comparison<Address> match)
{
List<Address> items = (List<Address> )Items;
items.Sort(match);
}
protected internal IEnumerable<Address> FindAll(Predicate<Address> match)
{
List<Address> items = (List<Address> )Items;
return (IEnumerable<Address> )items.FindAll(match);
}
}
Later in your Person class you can have the methods to get the desired functionality
public Address FindAddressByStreet(String street)
{
return _addresses.Find(delegate(Address a)
{
return a.Street.StartsWith(street);
});
}
public IEnumerable<Address> FindAllAddressByState(String state)
{
return _addresses.FindAll(delegate(Address a)
{
return a.State == state;
});
}
public void SortAddressByState()
{
_addresses.Sort(delegate(Address a1, Address a2)
{
return a1.State.CompareTo(a2.State);
});
}
No comments:
Post a Comment