.Net Tips – Restricting attribute usage to specific type

Yan Cui

I help clients go faster for less using serverless technologies.

Before you get your hopes up, sorry, there’s no built-in support to allow you to restrict the usage of your attribute to a type easily. That’s not to say that it’s impossible though!

If you’re reading this, I assume that you’ve tried to write a custom attribute yourself and realised that the options available in the AttributeTargets enum is fairly limited.

For those of you who are familiar with PostSharp, you would probably have noticed that one of the methods you can override is curiously called CompileTimeValidate and returns a boolean value. You can use the MethodBase argument passed into this method to inspect the type the attribute has been applied to and perform some validation logic there, e.g.:

[Serializable]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method)]
public class MyAttribute : OnMethodBoundaryAspect
{
    public override bool CompileTimeValidate(System.Reflection.MethodBase method)
    {
        var type = method.DeclaringType;

        // if the attribute is used outside the Service class, fail the build
        if (!type.Equals(typeof(Service)))
        {
            throw new Exception("MyAttribute can only be used in the Service class");
        }

        return true;
    }
}

Because you have the metadata for the method in hand, there’s nothing to stop you from restricting the usage of your attribute by input and return types too, e.g. attribute can only be applied to a method which returns string, etc.

If this is too much work for your liking, Marc Gravell blogged about a neat trick you could do to achieve the same effect here. The trick exploits the protected modifier in C# so that an attribute declared in the base type can be used by derived types but not by others. Here’s the code snippet from his original post:

abstract class MyBase
{
    [AttributeUsage(AttributeTargets.Property)]
    protected sealed class SpecialAttribute : Attribute {}
}

class ShouldBeValid : MyBase
{
    [Special] // works fine
    public int Foo { get; set; }
}

class ShouldBeInvalid
{
    // not a subclass of MyBase
    [Special] // type or namespace not found
    [MyBase.Special] // inaccessible due to protection level
    public int Bar{ get; set; }
}

Update: I saw another related question on StackOverflow here, as far as I’m aware you won’t be able to do this without the help of PostSharp as you won’t have access to the access modifiers (public, protected, internal or private) that was applied to the field when it was declared. With PostSharp however, this is how you would could go about doing such validation at compile time and make sure the attribute is applied to a public or protected field:

[Serializable]
[AttributeUsage(AttributeTargets.Field)]
public class MyAttribute : OnFieldAccessAspect
{
    public override bool CompileTimeValidate(System.Reflection.FieldInfo field)
    {
        if (field.IsPublic || field.IsFamily)
        {
            throw new Exception("Attribute can only be applied to Public or Protected fields");
        }

        return true;
    }
}

Whenever you’re ready, here are 3 ways I can help you:

  1. Production-Ready Serverless: Join 20+ AWS Heroes & Community Builders and 1000+ other students in levelling up your serverless game.
  2. Consulting: If you want to improve feature velocity, reduce costs, and make your systems more scalable, secure, and resilient, then let’s work together and make it happen.
  3. Join my FREE Community on Skool, where you can ask for help, share your success stories and hang out with me and other like-minded people without all the negativity from social media.

 

1 thought on “.Net Tips – Restricting attribute usage to specific type”

  1. Hay Great…

    Thanks for sharing this great tips with us. This will relay help a developer to build an application easily.

    So, please keep updates in it.

Leave a Comment

Your email address will not be published. Required fields are marked *