How to use custom attributes in C#

By FoxLearn 1/18/2025 3:00:29 AM   68
Attributes are used to attach additional metadata to classes, methods, properties, or other program elements.

The Description attribute stores a user-friendly description of a class or property, which can be read and displayed during runtime.

Unit testing frameworks identify test methods by looking for the TestMethod attribute.

Web APIs often use attributes like HttpGet and validation attributes such as Required for model validation.

Create the Custom Attribute

To create a custom attribute, you need to inherit from the Attribute class.

Here's an example of a custom attribute that stores the priority level of a task:

public class PriorityLevelAttribute : Attribute
{
    public int Priority { get; }

    public PriorityLevelAttribute(int priority)
    {
        Priority = priority;
    }
}

Attribute constructor parameters must be constant values, so you cannot pass variables or objects at runtime, only constants like numbers or enums.

Apply the Attribute

Next, you can apply this PriorityLevelAttribute to an enum or class. Here, we apply it to an enum representing different task statuses:

public enum TaskStatus
{
    [PriorityLevel(1)]
    HighPriority,
    
    [PriorityLevel(2)]
    MediumPriority,
    
    [PriorityLevel(3)]
    LowPriority
}

In this example, we’ve set different priority levels for each status of a task.

Retrieve the Attribute Value at Runtime

To access the attribute value at runtime, you'll use reflection. Below is an example of how to extract the priority value from a TaskStatus enum:

using System;
using System.Reflection;
using System.Linq;

public static class TaskStatusExtensions
{
    public static int GetPriority(this TaskStatus status)
    {
        Type taskStatusType = typeof(TaskStatus);
        string statusName = Enum.GetName(taskStatusType, status);
        MemberInfo[] memberInfo = taskStatusType.GetMember(statusName);

        if (memberInfo.Length != 1)
        {
            throw new ArgumentException($"TaskStatus of {status} should only have one memberInfo");
        }

        IEnumerable<PriorityLevelAttribute> customAttributes = memberInfo[0].GetCustomAttributes<PriorityLevelAttribute>();
        PriorityLevelAttribute priorityAttribute = customAttributes.FirstOrDefault();

        if (priorityAttribute == null)
        {
            throw new InvalidOperationException($"TaskStatus of {status} has no PriorityLevelAttribute");
        }

        return priorityAttribute.Priority;
    }
}

Alternatively, you can use a more concise version by creating a generic method to retrieve any attribute:

public static class TaskStatusExtensions
{
    private static T GetAttribute<T>(this TaskStatus status) 
        where T : Attribute
    {
        return (status.GetType().GetMember(Enum.GetName(status.GetType(), status))[0]
                .GetCustomAttributes(typeof(T), inherit: false)[0] as T);
    }

    public static int GetPriority(this TaskStatus status)
    {
        return status.GetAttribute<PriorityLevelAttribute>().Priority;
    }
}

Use the Attribute in Practice

Now you can use the custom attribute to determine the priority of a task and act accordingly.

Console.WriteLine("Task processing...");

Task[] tasks = LoadTasks(); // implementation not shown

foreach (var task in tasks)
{
    int priority = task.Status.GetPriority();
    Console.WriteLine($"Task {task.Name} has priority level {priority}");
    
    // Simulate processing based on priority
    if (priority == 1)
    {
        Console.WriteLine("Processing high-priority task first.");
    }
    else if (priority == 2)
    {
        Console.WriteLine("Processing medium-priority task next.");
    }
    else
    {
        Console.WriteLine("Processing low-priority task last.");
    }
}

Console.ReadKey();

In this example, we use the GetPriority extension method to fetch the priority level of each task and determine its processing order based on the priority level defined with the PriorityLevelAttribute.