c# CustomCheckAttribute

How can I write a custom attribute that targets whatever property it is attached to and that checks the logic of the boolean expression?

If the check is false I want the setter method to fail.

Example implementation:

public class CheckAttribute : Attribute
{
    public CheckAttribute(int checkvalue, Operation op)
    {
        int val = 0;
        System.Linq.Expressions.Expression exp = System.Linq.Expressions.Expression.Condition(op switch
        {
            Operation.Equals => System.Linq.Expressions.Expression.Constant(val == checkvalue),
            Operation.GtrThan => System.Linq.Expressions.Expression.Constant(val > checkvalue),
            Operation.LssThan => System.Linq.Expressions.Expression.Constant(val < checkvalue),
            Operation.GrtOrEquals => System.Linq.Expressions.Expression.Constant(val >= checkvalue),
            Operation.LssOrEquals => System.Linq.Expressions.Expression.Constant(val <= checkvalue),
            Operation.Neq => System.Linq.Expressions.Expression.Constant(val != checkvalue),
            _ => throw new InvalidOperationException()
        },
        System.Linq.Expressions.Expression.Constant(true),
        System.Linq.Expressions.Expression.Constant(false));

        if (!System.Linq.Expressions.Expression.Lambda<Func<bool>>(exp).Compile()())
        {
            throw new InvalidOperationException("Check condition fails");
        };    
    }
}

usage:

class MyClass 
{
    [CheckAttribute(0, CheckAttribute.Operation.LssThan)]
    public int MinValue { get; set; }

    [CheckAttribute(10, CheckAttribute.Operation.GtrThan)]
    public int MaxValue { get; set; }
}

If any code instantiates MyClass and sets MinValue to a value less than 0, I want an exception at runtime.
Similarly, if any code instantiates MyClass and sets MaxValue to a value greaer than 10, I want an exception.

It would be excelent if exception could be thrown at compile time, but I suppose it would need some magic with System.Runtime.CompileServices, if that’s even possible.

In short, I want to write a custom attribute which constrains property values on assignment via the setter method, and not explicity code logic to every setter property to check the attribute value.

  • 1

    Attributes don’t cause any code to fire, they’re just metadata. Something has to check the attribute and run the associated method. Why do you want to do this with an attribute specifically? Is there a requirement that doesn’t allow you to use a type or custom setter logic?

    – 

  • I dont think there’s out of the box implementation for that, you might need a proxy to that object and a helper to instantiate your object for it to handle your attributes upon setting value (setter), CastleCore maybe?

    – 

  • 1

    Aspect-oriented programming may help you. I recommend the AOP in .NET book.

    – 

  • Look at Fody: MethodBoundaryAspect, MethodDecorator.

    – 

  • But only a Roslyn static code analyzer can ideally solve your problem. How to write a Roslyn Analyzer

    – 

This is not a job for an attribute. Attributes do not cause any logic to fire by themselves, they’re just metadata. Some other code has to read the attribute and react to it.

So with an attribute you could only have an external validator that would check the values of the properties after the fact and then threw an exception.

using System;
using System.Reflection;

var instance = new MyClass
{
    MinValue = 5,
    MaxValue = 11,
};

Validate(instance);

void Validate<T>(T instance)
{
    foreach (PropertyInfo property in typeof(T).GetProperties())
    {
        var attribute = property.GetCustomAttribute<CheckAttribute>();
        
        if (attribute is not null && property.GetValue(instance) is int val)
        {
            attribute.Validate(val);
        }
    }
}

class MyClass 
{
    [CheckAttribute(0, CheckAttribute.Operation.LssThan)]
    public int MinValue { get; set; }

    [CheckAttribute(10, CheckAttribute.Operation.GtrThan)]
    public int MaxValue { get; set; }
}

public class CheckAttribute : Attribute
{
    private readonly int _checkValue;
    private readonly Operation _op;

    public CheckAttribute(int checkValue, Operation op) =>
        (_checkValue, _op) = (checkValue, op);

    public void Validate(int val)
    {
        var violated = _op switch
        {
            Operation.Equals => val == _checkValue,
            Operation.GtrThan => val > _checkValue,
            Operation.LssThan => val < _checkValue,
            Operation.GrtOrEquals => val >= _checkValue,
            Operation.LssOrEquals => val <= _checkValue,
            Operation.Neq => val != _checkValue,
            _ => throw new ArgumentOutOfRangeException("op")
        };
        
        if (violated)
        {
            throw new InvalidOperationException("Check condition fails");
        };
    }

    public enum Operation
    {
        Equals,
        GtrThan,
        LssThan,
        GrtOrEquals,
        LssOrEquals,
        Neq,
    }
}

SharpLab link.

Leave a Comment