Tuesday, April 1, 2008

DoNotExposeGenericLists error and Using Collection<T> instead of List<T>

If you have ever exposed a List<T> from a method / Property in a .NET assembly code, while running FxCop you are supposed to get the ‘DoNotExposeGenericLists’ error as follow.

Do not expose List<T> in object models. Use Collection<T>, ReadOnlyCollection<T> or KeyedCollection instead. List<T> is meant to be used from implementation, not in object model API. List<T> is optimized for performance at the cost of long term versioning. For example, if you return List<T> to the client code, you will not ever be able to receive notifications when client code modifies the collection.

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: