AOP Custom Contract: ListNotEmptyAttribute

So I have a List and I need to make sure that the list has at least one value when it is passed in. If the list is empty, I should throw and exception. Here is an example of what the code would look like if you did NOT use an aspect:

using System;
using System.Collections.Generic;

namespace CustomContractsExample
{
    public class People
    {
        private readonly List<Person> _People;

        public People(List<Person> people)
        {
            if (people == null)
                throw new ArgumentNullException("people", "The list cannot be null.");
            if (people.Count == 0)
                throw new ArgumentException("people", "The list cannot be empty.");
            _People = people;
        }

        public List<Person> List
        {
            get { return _People; }
        }
    }
}

Note: My use case is not actually a People object with a List. It is instead something proprietary for my company and a far more valid use case. I am using the People class for simplicity in demonstration only.

I decided to handle this precondition checking not in the methods, but in an Aspect. Particularly, by using a PostSharp LocationContractAttribute. I recently wrote a post about this here:
AOP Contracts with PostSharp

So we need to create a new custom contract as I didn’t find one written by PostSharp. At first, I wondered why not. Why not create a quick generic attribute like this:

using System.Collections.Generic;
using PostSharp.Aspects;
using PostSharp.Patterns.Contracts;
using PostSharp.Reflection;

namespace CustomContractsExample
{
    public class ListNotEmptyAttribute<T> : LocationContractAttribute, ILocationValidationAspect<List<T>>
    {
        new public const string ErrorMessage = "The List<T> must not be empty.";

        protected override string GetErrorMessage()
        {
            return "The List<T> must not be empty: {2}";
        }

        public System.Exception ValidateValue(List<T> value, string locationName, LocationKind locationKind)
        {
            if (value == null)
                return CreateArgumentNullException(value, locationName, locationKind);
            if (value.Count == 0)
                return CreateArgumentException(value, locationName, locationKind);
            return null;
        }
    }
}

Well, the reason is because C# doesn’t support generic attributes. I get this error at compile time:

A generic type cannot derive from ‘LocationContractAttribute’ because it is an attribute class

This is a tragedy. What makes it more of a tragedy is that I could do this if I wrote directly in IL. It is simply a compiler limitation for C#. Arrrgggss!!!! Good thing MSBuild is going open source at https://github.com/Microsoft/msbuild. Hopefully, the DotNet team, or some interested party such as PostSharp, or maybe me, contributes a few changes to MSBuild and removes this limitation.

As for now, List also implements IList, so I will revert to using that. IList provided a workaround for this use case, however, such a work around won’t always be available.

using System.Collections;
using PostSharp.Aspects;
using PostSharp.Patterns.Contracts;
using PostSharp.Reflection;

namespace CustomContractsExample
{
    public class ListNotEmptyAttribute : LocationContractAttribute, ILocationValidationAspect<IList>
    {
        new public const string ErrorMessage = "The List must not be empty.";

        protected override string GetErrorMessage()
        {
            return "The list must not be empty: {2}";
        }

        public System.Exception ValidateValue(IList value, string locationName, LocationKind locationKind)
        {
            if (value == null)
                return CreateArgumentNullException(value, locationName, locationKind);
            if (value.Count == 0)
                return CreateArgumentException(value, locationName, locationKind);
            return null;
        }
    }
}

Now here is the new People class. See how it is much cleaner.

using System.Collections.Generic;

namespace CustomContractsExample
{
    public class People
    {
        private readonly List<Person> _People;

        public People([ListNotEmpty]List<Person> people)
        {
            _People = people;
        }

        public List<Person> List
        {
            get { return _People; }
        }
    }
}

The constructor is much cleaner and easier to read.

Also, my unit tests pass.

using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using CustomContractsExample;

namespace CustomContractsExampleTests
{
    [TestClass]
    public class PeopleTests
    {
        // Arrange
        private const string Firstname = "Jared";
        private const string LastName = "Barneck";

        [TestMethod]
        public void TestNewPersonWorks()
        {
            var person = new Person(Firstname, LastName);
            var list = new List<Person> { person };
            var people = new People(list);

            Assert.IsNotNull(people);
            Assert.IsFalse(people.List.Count == 0);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void TestNewPersonThrowsExceptionIfFirstNameNull()
        {
            new People(null);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void TestNewPersonThrowsExceptionIfLastNameNull()
        {
            new People(new List<Person>());
        }
    }
}

Maybe PostSharp can pick this up my ListNotEmptyAttribute and add it to their next released version.

Leave a Reply

How to post code in comments?