IList<> implementation with more strict contract
-
Wednesday, July 07, 2010 1:08 PM
Hi all,
I'd like to create a collection derived from IList<>, which does not allow an item to be null. That is, I need to put Contract.Requires<ArgumentNullException>( item != null ) in all methods which take 'item' as parameter. What is the proper way to do it? If I put it in the implementation, I get warning CC1033, and I found no way how to create contract class for such a collection.
This is what I tried and obviously does not work (the Requires check in IndexOf method of contract class does not get called - I guess IndexOf would have to be virtual method for the contract class to be able to add contracts to it)...
[ContractClass( typeof( ElementCollectionContract<,> ) )] public abstract class ElementCollection<TContainer, TElement> : IList<TElement>, IElementCollection { public int IndexOf( TElement element ) { ... } ... } [ContractClassFor( typeof( ElementCollection<,> ) )] public abstract class ElementCollectionContract<TContainer,TElement> : ElementCollection<TContainer, TElement> { public int IndexOf( TElement element ) { Contract.Requires<ArgumentNullException>( element != null, "element" ); throw new NotSupportedException(); } }
Is there some way to create contracts for IList<> which would only apply to selected implementation?
-md-
All Replies
-
Wednesday, July 07, 2010 1:40 PM
Hi,
When your method is not abstract, such as in IndexOf, you place any contracts in the method body itself, and not in a contract class. So in this case, there is no need for a contract class. The short answer is no - you cannot add Requires to any IList<TElement> methods.
The reason that you get CC1033 when doing so is that if a method Foo wanted an IList<TElement>, you could then pass in ElementCollection<TContrainer, TElement>. Now, all Foo knows or cares about is that the argument is an IList<TElement>, and if Foo wanted to call IList<TElement>.IndexOf(element), it might want to pass in null values, because it doesn't know that this specific IList<T> cannot accept null values for arguments. Foo might then call IndexOf with a null element, thinking that everything will work fine (as it should), and yet the contract is violated, and and exception thrown. To prevent this problem, any methods implementing or overriding a base method cannot add Requires. Note that overriding/implementing methods can add Ensures, since all of the base method's Ensures will be true regardless.
To fix this, you might consider not deriving from IList<T> - is it necessary to do so? Otherwise, you may want to explicitly implement IList<T>'s methods without Requires, and then normal versions of said method with the Requires, such as in
int IList<TElement>.IndexOf(TElement element) { // implementation } public int IndexOf(TElement element) { Contract.Requires(element != null); return ((IList<TElement>)this).IndexOf(element); }This is a bit of a mess, but it lets code that is aware of the non-null restriction of ElementCollection ensure that all contracts are valid, while code that only knows about IList<T> will function as expected.
If you really want IList<T> methods to throw errors when null is passed in, you'll probably have to do it the old fashioned way of if (element == null) throw ... However, this violates the principle that code manipulating a class should not have to know details of subclasses. It really depends on the code using ElementCollection, though, whether you want to drop IList<T> completely, implement IEnumerable<T> instead, write duplicate methods for IList<T> and ElementCollection, or just throw exceptions always.
As an aside, this is similar to the problem of using T[] as IList<T> - a call to IList<T>.Add would throw an exception, which is not expected (arguably, one should examine the IsReadOnly property, which does exist in IList<T>, but an alternative implementation might have considered IList<T> deriving from IReadableList<T>, or similar).
- Jamie- Marked As Answer by Martin Dvorak Thursday, July 08, 2010 5:36 PM
-
Thursday, July 08, 2010 5:45 PM
Understood, thanks for excellent explanation. I thought this would be the case. I guess, from the contracts point of view, the right solution would be to drop IList<> completely because the contract's different, but I'm not going to do it since I need to be able to pass references to ElementCollection to methods which take IList<> or IList as an argument.
-md-

