Custom attributes in .NET (Core) are a helpful mechanism to attach additional information to classes, structs, and even their members. In this article, we’re going to explain how to create, access, and get the information from custom attributes in .NET through some practical examples.
Let’s start.
Declaring Custom Attributes
We can define an attribute by creating a class. This class should inherit from the Attribute
class.
Microsoft recommends appending the ‘Attribute’ suffix to the end of the class’s name. After that, each property of our derived class will be a parameter of the desired data type.
Customizing Custom Attribute Usage
The AttributeUsageAttribute
class specifies the usage of another attribute class by defining some of the fundamental features.
This class has three members:
- AttributeTargets enum
- Inherited property (bool)
- AllowMultiple property (bool)
AttributeTargets Enum
The AttributeTargets
enum specifies application elements we can apply our custom attribute to.
To see how it works, let’s create a new class called TaskDescriptorAttribute
:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public class TaskDescriptorAttribute : Attribute { public string? Name { get; set; } public string? Description { get; set; } public bool NeedsManager { get; set; } public int DeveloperCount { get; set; } }
We can apply this attribute (TaskDescriptorAttribute
) just to the classes and structures because we have set its target to both using a bitwise combination. Of course, other than classes and structs, we can use methods, enums, and other application elements when creating attributes. Those attributes also apply to all of the application elements if we use AttributeTargets.All
value (it’s the default).
Let’s use our attribute with the MyTasks
class:
[TaskDescriptor(Name = "The task's name", Description = "Some descriptions for the task", NeedsManager = true, DeveloperCount = 5)] public class MyTasks { }
When we apply our TaskDescriptorAttribute
to a class, we just use the TaskDescriptor
part because the compiler lets us use it without the ‘Attribute’ suffix.
AllowMultiple Property
The AllowMultiple
property permits multiple instances of our attribute. This property can be either false
(the default) or true
.
Let’s create another attribute called DeveloperTaskAttribute
:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class DeveloperTaskAttribute : Attribute { public Priorities Priority { get; set; } public string? Description { get; set; } public DeveloperTaskAttribute(Priorities priority) { Priority = priority; } }
We can use this attribute only on methods. And we can apply multiple instances of it to them. It has the required Priority
and the optional Description
parameters.
To apply this attribute, we are going to create a new ScheduleMeeting()
method inside the MyTasks
class:
public class MyTasks { [DeveloperTask(Priorities.Low)] [DeveloperTask(Priorities.High, Description = "High level description")] public void ScheduleMeeting() { } }
The ScheduleMeeting()
method has two DeveloperTask
attributes now. We declare the first one with only the required parameter but the second one with both the required and optional parameters. We cannot define a DeveloperTask
attribute without a Priorities
parameter though, or we’ll get a compiler error.
Inherited Property
The Inherited
property is another key feature that we can apply to a custom attribute. It indicates whether that attribute can be inherited. This property has a default value of true
.
To see the usage of this property, let’s create the ManagerTaskAttribute
attribute:
[AttributeUsage(AttributeTargets.Method, Inherited = false)] public class ManagerTaskAttribute : Attribute { public Priorities Priority { get; set; } public bool NeedsReport { get; set; } }
This new attribute cannot be inherited, and both of its parameters are optional.
Now, we are going to create the ScheduleInterview
method in the MyTasks
class to utilize it:
public class MyTasks { [ManagerTask(Priority = Priorities.Mid, NeedsReport = true)] [DeveloperTask(Priorities.High, Description = "High level description")] public virtual void ScheduleInterview() { } }
This method has two attributes, one DeveloperTask
and one ManagerTask
attribute each. We also add the virtual
keyword because we want to override it in another class.
So, let’s create the YourTasks
class that inherits from the MyTasks
class:
public class YourTasks : MyTasks { [DeveloperTask(Priorities.Mid, Description = "Mid level description")] public override void ScheduleInterview() { } }
The ScheduleInterview
method inside the YourTasks
inherited class overrides the previous ScheduleInterview
method from the MyTasks
base class. This method does not have the ManagerTask
attribute because its Inherited
property has a value of false
.
But, the DeveloperTask
attribute has a default Inherited
value of true
. So, the YourTasks.ScheduleInterview
method has two DeveloperTask
attributes. We have declared the first one inside the YourTasks
class and the second one inside the MyTasks
class.
Access an Instance of a Custom Attribute
Once we want to retrieve the values from our attributes, we can use the static GetCustomAttribute
method from the Attribute
class. So, let’s create the GetAttribute
method for acquiring the information stored in the TaskDescriptor
instance:
public static string? GetAttribute(Type desiredType, Type desiredAttribute) { var attributeInstance = Attribute.GetCustomAttribute(desiredType, desiredAttribute); if (attributeInstance == null) Console.WriteLine($"The class {desiredType} does not have atributes."); else WriteOnTheConsole(attributeInstance); return attributeInstance?.ToString(); }
Our GetAttribute
method expects a (class) type and an attribute type as input parameters and prints the information on the console.
Inside the Attribute
base class, we can find different overloads of the GetCustomAttribute
method. For our example, we use the GetCustomAttribute(MemberInfo element, Type attributeType)
overload to get our desired information.
The Type
inherits from the MemberInfo
base class so, we can pass it to the method as the first argument. We also send our custom attribute’s type as the second argument. We print all information on the console using a bit of reflection inside the WriteOnTheConsole()
method. (you can check the source code for implementation)
Creating an Instance of a Custom Attribute
The GetCustomAttribute
method returns either an instance of the attribute or a null
value. So, the attributeInstance
variable stores an instance of our custom attribute if it does exist. Now, we are going to retrieve the information of that instance.
Retrieving the Information of a Custom Attribute
To retrieve the information of our custom attribute, we are going to call the GetAttribute
method and pass typeof(MyTasks)
and typeof(TaskDescriptorAttribute)
as its arguments:
GetAttribute(typeof(MyTasks), typeof(TaskDescriptorAttribute));
If the GetAttribute
method finds an instance of the TaskDescriptorAttribute
class, we should get all of its properties as a result:
The CustomAttributes.TaskDescriptorAttribute attribute: The Name property is: The task's name The Description property is: Some descriptions for the task The NeedsManager property is: True The DeveloperCount property is: 5
We retrieved the information of a custom attribute class successfully.
Getting the Instances of Different Custom Attributes
Sometimes, we need to access all attributes of the class’s members. The Attribute
base class has another GetCustomAttributes
method to return them as an array.
Let’s create the GetAttributesOfMethods
method to access instances of all attributes and retrieve their information:
public static List<string> GetAttributesOfMethods(Type elementType) { List<string> attributes = new List<string>(); var methodInfoList = elementType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); if (methodInfoList == null || methodInfoList.Length == 0) { Console.WriteLine($"The type {elementType} does not have any methods."); return attributes; } foreach (var methodInfo in methodInfoList) { var attributeList = Attribute.GetCustomAttributes(methodInfo, true); if (attributeList.Length == 0) { Console.WriteLine($"The {elementType.Name}.{methodInfo.Name} method does not have attributes."); continue; } Console.WriteLine($"The {elementType.Name}.{methodInfo.Name} method's attribute:"); foreach (var att in attributeList) { WriteOnTheConsole(att); attributes.Add(methodInfo.Name + "-" + att.ToString()); } Console.WriteLine(); } return attributes; }
We want to obtain all of the attributes of every declared method. So, we call the GetMethods
method using appropriated enums.
Inside the first foreach
loop, we call the GetCustomAttributes
method using the GetCustomAttribute(MemberInfo element, bool inherit)
overload and send the fetched MethodInfo
values, one by one. We also pass true
as the second argument because we need the inherited attributes.
Inside the inner foreach
loop, we sequentially fetch items of the attributeList
array and print them on the console using the WriteOnTheConsole
method.
Now, let’s call the GetAttributesOfMethods
method for the MyTasks
class:
GetAttributesOfMethods(typeof(MyTasks));
We send the type of the MyTasks
class to this method and as a result, we expect to see all the attributes of its methods:
The MyTasks.ScheduleMeeting method's attribute: The CustomAttributes.DeveloperTaskAttribute attribute: The Description property is: The Priority property is: Low The CustomAttributes.DeveloperTaskAttribute attribute: The Description property is: High level description The Priority property is: High The MyTasks.ScheduleInterview method's attribute: The CustomAttributes.ManagerTaskAttribute attribute: The Priority property is: Mid The NeedsReport property is: True The CustomAttributes.DeveloperTaskAttribute attribute: The Description property is: High level description The Priority property is: High
The GetAttributesOfMethods
prints the attributes of ScheduleMeeting
and ScheduleInterview
methods respectively.
We can do the same for the YourTasks
class:
GetAttributesOfMethods(typeof(YourTasks));
And find the results very similar:
The YourTasks.ScheduleInterview method's attribute: The CustomAttributes.DeveloperTaskAttribute attribute: The Description property is: Mid level description The Priority property is: Mid The CustomAttributes.DeveloperTaskAttribute attribute: The Description property is: High level description The Priority property is: High
We don’t see the ManagerTask
attribute on the console because this attribute has an Inherited
value of false
. The reason we see two DeveloperTask
attributes is because we’ve declared the first one inside the MyTasks
class and the second inside the YourTasks
class.
Conclusion
In this article, we have learned how to declare custom attributes in .NET. We’ve seen that we can utilize them for classes and their members. We also have touched upon the access ways to the single and multiple attribute instances. Finally, we have found out how to retrieve their information.