Creating Custom Contracts

Given the benefits that contracts provide over manually checking values and throwing exceptions in code, you will likely want to implement your own contracts to perform your own custom checks and handle your own custom types.

The following steps show how to implement a contract which throws an exception if a numeric parameter is zero:

To implement a contract throwing an exception if a numeric parameter is zero:

  1. Use the following namespaces: PostSharp.Aspects and PostSharp.Reflection.

  2. Derive a class from LocationContractAttribute and override the GetErrorMessage() method:

    C#
    public class NonZeroAttribute : LocationContractAttribute
    {
        public const string ErrorMessage = "NonZeroErrorMessage";
    
        public NonZeroAttribute()
          : base()
        {
        }
    
        protected override string GetErrorMessage()
        {
            return "Value {2} must have a non-zero value.";
        }
    }
    Note Note

    The value returned by GetErrorMessage() method can contain formatting placeholders. See the remarks section for the LocationContractAttribute class for more information.

  3. Implement the ILocationValidationAspect interface in the new contract class which exposes the ValidateValue(T, String, LocationKind) method. Note that this interface must be implemented for each type that is to be handled by the contract. In this example, the contract will handle both int and uint, so the interface is implemented for both integer types. If additional integer types were to be handled by this class (e.g. long), then additional implementations of ILocationValidationAspect would have to be added:

    C#
    public class NonZeroAttribute : LocationContractAttribute, ILocationValidationAspect<int>, ILocationValidationAspect<uint>
    {
        public const string ErrorMessage = "NonZeroErrorMessage";
    
        public NonZeroAttribute()
          : base()
        {
        }
    
        protected override string GetErrorMessage()
        {
           return "Value {2} must have a non-zero value.";
        }
    
        public Exception ValidateValue(int value, string name, LocationKind locationKind)
        {
           if (value == 0)
             return this.CreateArgumentOutOfRangeException(value, name, locationKind);
          else
             return null;
        }
    
        public Exception ValidateValue(uint value, string name, LocationKind locationKind)
        {
            if (value == 0)
              return this.CreateArgumentOutOfRangeException(value, name, locationKind);
            else
              return null;
        }
    }

    The ValidateValue(T, String, LocationKind) method takes in the value to test, the name of the parameter, property or field, and the usage (i.e. whether it’s a parameter, property, or field). The method must return an exception if a check fails, or null or if no exception is to be raised.

With the contract now created it can be used. For example, the following methods which calculate the modulus between two numbers, can use the contract defined above to ensure that neither of their input parameters are zero:

C#
bool Mod([NonZero] int number, [NonZero] int dividend)
{
  return ((number % dividend) == 0);
}

bool Mod([NonZero] uint number, [NonZero] uint dividend)
{
  return ((number % dividend) == 0);
}