Search code examples
c#postsharp

PostSharp Contracts Range


Is it possible to achieve something like this with PostSharp and Contracts?

public class Something
{
    int number = 10;

    public void Remove([Range(1,this.number)] int remove)
    {
        number -= remove;
    }

    public void Add(int add)
    {
        number += add;
    }
}

Solution

  • C# compiler will not allow you to apply the [Range] attribute in this way - you will receive a build error stating that "an attribute argument must be a constant expression, typeof expression or array creation expression".

    The workaround is to create an aspect that accepts a field name as an argument. Then import that field into the aspect, so you can read the current max value.

    [Serializable]
    public class MyRangeAttribute : LocationContractAttribute,
                                    ILocationValidationAspect<int>,
                                    IInstanceScopedAspect,
                                    IAdviceProvider
    {
        [NonSerialized]
        private object instance;
        [NonSerialized]
        private string maxValueFieldName;
        private int minValue;
        public ILocationBinding maxValueFieldBinding;
    
        public MyRangeAttribute(int minValue, string maxValueFieldName)
        {
            this.minValue = minValue;
            this.maxValueFieldName = maxValueFieldName;
        }
    
        public Exception ValidateValue(int value, string locationName, LocationKind locationKind)
        {
            int maxValue = (int) this.maxValueFieldBinding.GetValue(ref this.instance, Arguments.Empty);
            if (value < minValue || value > maxValue)
                return new ArgumentOutOfRangeException(locationName);
    
            return null;
        }
    
        public IEnumerable<AdviceInstance> ProvideAdvices(object targetElement)
        {
            FieldInfo maxValueField = ((LocationInfo)targetElement).DeclaringType
                .GetField( this.maxValueFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
    
            yield return new ImportLocationAdviceInstance(
                typeof (MyRangeAttribute).GetField("maxValueFieldBinding"),
                new LocationInfo(maxValueField));
        }
    
        public object CreateInstance( AdviceArgs adviceArgs )
        {
            MyRangeAttribute clone = (MyRangeAttribute) this.MemberwiseClone();
            clone.instance = adviceArgs.Instance;
            return clone;
        }
    
        public void RuntimeInitializeInstance()
        {
        }
    }
    

    You can apply this aspect like this:

    public class Something
    {
        private int number = 10;
    
        public void Remove([MyRange(1, "number")] int remove)
        {
            number -= remove;
        }
    
        public void Add(int add)
        {
            number += add;
        }
    }