Search code examples
c#.netcode-contracts

Code contracts static checker: Possibly calling a method on a null reference — why?


I have the following class:

public class AndSpecification<TEntity> : ISpecification<TEntity>
{
    protected ISpecification<TEntity> First { get; private set; }
    protected ISpecification<TEntity> Second { get; private set; }

    public bool IsSatisfiedBy(TEntity entity)
    {
        return First.IsSatisfiedBy(entity) && Second.IsSatisfiedBy(entity);
    }

    public AndSpecification(ISpecification<TEntity> first, ISpecification<TEntity> second)
    {
        Contract.Requires(first != null);
        Contract.Requires(second != null);
        Contract.Ensures(First != null);
        Contract.Ensures(Second != null);

        First = first;
        Second = second;
    }
}

Note that I use Contract.Ensures() to ensure that after the constructor is invoked, First and Second are never null. However, Code Contracts give me a warning in IsSatisfiedBy() method implementation: CodeContracts: Possibly calling a method on a null reference 'this.First' (same for the Second). Can't figure out what's wrong, is that me or the static checker?


Solution

  • It appears you are missing some contracts and object invariants. I took your code above and ran Code Contracts static analysis on it and got exactly what you got. Then I worked my way through the code to arrive at an implementation that satisfies the static analyzer.

    [ContractClass(typeof(ISpecificationContracts<>))]
    public interface ISpecification<TEntity> 
        where TEntity : class
    {
        bool IsSatisfiedBy(TEntity entity);
    }
    
    [ContractClassFor(typeof(ISpecification<>))]
    abstract class ISpecificationContracts<TEntity> 
        : ISpecification<TEntity> where TEntity : class
    {
        public bool IsSatisfiedBy(TEntity entity)
        {
            Contract.Requires(entity != null);
            throw new NotImplementedException();
        }
    }
    
    public class AndSpecification<TEntity> 
        : ISpecification<TEntity> where TEntity : class
    {
        private readonly ISpecification<TEntity> _first;
        private readonly ISpecification<TEntity> _second;
    
        protected ISpecification<TEntity> First {
            get
            {
                Contract.Ensures(Contract.Result<ISpecification<TEntity>>() != null);
                return _first;
            }
        }
        protected ISpecification<TEntity> Second
        {
            get
            {
                Contract.Ensures(Contract.Result<ISpecification<TEntity>>() != null);
                return _second;
            }
        }
    
        public bool IsSatisfiedBy(TEntity entity)
        {
            return First.IsSatisfiedBy(entity) && Second.IsSatisfiedBy(entity);
        }
    
        public AndSpecification(ISpecification<TEntity> first,
                                ISpecification<TEntity> second)
        {
            Contract.Requires(first != null);
            Contract.Requires(second != null);
            Contract.Ensures(_first == first);
            Contract.Ensures(_second == second);
    
            _first = first;
            _second = second;
        }
    
        [ContractInvariantMethod]
        [System.Diagnostics.CodeAnalysis.SuppressMessage(
            "Microsoft.Performance", 
            "CA1822:MarkMembersAsStatic", 
            Justification = "Required for code contracts.")]
        private void ObjectInvariant()
        {
            Contract.Invariant(_first != null);
            Contract.Invariant(_second != null);
        }
    
    }