AOP Custom Contract: ListNotEmptyAttribute
So I have a List
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
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
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.

