none
IList<> implementation with more strict contract

    Question

  • 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-
    Wednesday, July 07, 2010 1:08 PM

Answers

  • 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
    Wednesday, July 07, 2010 1:40 PM

All replies

  • 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
    Wednesday, July 07, 2010 1:40 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-
    Thursday, July 08, 2010 5:45 PM