C# – Using custom attributes

Attributes are used to store additional info about a class/method/property. The attributes are read at runtime and used to change the program’s behavior. Here are a few examples of commonly used built-in attributes:

  • The Description attribute is used to stored a user-friendly description of a class/property that you can read and show to the user at runtime.
  • The unit testing framework discovers test methods to run by looking for methods having the TestMethod attribute.
  • Web APIs use many attributes, such as HttpGet and model validation attributes like Required.

In general, you should try to use built-in attributes when possible. When it makes sense, you can create your own custom attribute. I’ll show how to do that.

Step 1 – Create the custom attribute

To create a custom attribute class, you’ll need to subclass Attribute. Here’s an example:


public class BackgroundColorAttribute : Attribute
{
	public ConsoleColor ConsoleColor { get; }
	public BackgroundColorAttribute(ConsoleColor consoleColor)
	{
		ConsoleColor = consoleColor;
	}
}
Code language: C# (cs)

NOTE: Attribute constructor parameters must be constant values. This is why I’m using the ConsoleColor enum here.

Step 2 – Use the attribute

Now use the attribute using the special syntax – [AttributeName(parameters)]. Here’s an example of using the BackgroundColor attribute that was created in step 1:

public enum DeviceStatus
{
	[BackgroundColor(ConsoleColor.Green)]
	Registered,
	[BackgroundColor(ConsoleColor.Red)]
	PingFailed,
	[BackgroundColor(ConsoleColor.Yellow)]
	PortNotOpen,
	[BackgroundColor(ConsoleColor.Yellow)]
	RegistrationFailed,
	[BackgroundColor(ConsoleColor.Green)]
	FoundAndRegistered
}
Code language: C# (cs)

Notice that it’s unnecessary to use the word Attribute when using the attribute class. This is compiler magic.

Step 3 – Get the attribute value at runtime

Now in order to get the attribute value and use it at runtime, you’ll need to use reflection, as this example shows:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;

public static class DeviceStatusExtensions
{
	public static ConsoleColor GetBgColor(this DeviceStatus status)
	{
		Type deviceStatusType = typeof(DeviceStatus);
		string statusName = Enum.GetName(deviceStatusType, status);
		MemberInfo[] memberInfo = deviceStatusType.GetMember(statusName);

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

		IEnumerable<BackgroundColorAttribute> customAttributes = memberInfo[0].GetCustomAttributes<BackgroundColorAttribute>();
		BackgroundColorAttribute colorAttribute = customAttributes.FirstOrDefault();

		if(colorAttribute == null)
		{
			throw new InvalidOperationException($"DeviceStatus of {status} has no BackgroundColorAttribute");
		}

		return colorAttribute.ConsoleColor;
	}
}
Code language: C# (cs)

This code can be reduced a generic one-liner (without error handling and custom exceptions):

public static class DeviceStatusExtensions
{
	private static T GetAttribute<T>(this DeviceStatus status) 
		where T : System.Attribute
	{
		return (status.GetType().GetMember(Enum.GetName(status.GetType(), status))[0].GetCustomAttributes(typeof(T), inherit: false)[0] as T);
	}
	public static ConsoleColor GetBgColor(this DeviceStatus status)
	{
		return status.GetAttribute<BackgroundColorAttribute>().ConsoleColor;
	}
}
Code language: C# (cs)

Use the extension method shown above to extract the attribute value and use it:

Console.WriteLine("Fetching devices");
List<Device> devices = LoadDevices(); //implementation not shown

Console.WriteLine("Outputting current status of all devices...");

Console.ForegroundColor = ConsoleColor.Black;
foreach(var d in devices)
{

	Console.BackgroundColor = d.Status.GetBgColor();
	Console.WriteLine($"Device {d.IPAddress} Status={d.Status}");
}
Console.ResetColor();

Console.ReadKey();
Code language: C# (cs)

This example outputs the following color-coded messages to the console:

Console output showing statements with different background colors