C# – Using custom attributes

In this article I’ll be showing how to use custom attributes in C#. As an example, I’m creating a Console app that color codes output based on a status.

Here’s what this looks like:

What is an attribute?

First of all, what is an attribute?

Attributes are a clean way to add additional information about things (classes, methods, properties, enum values). This information can be used at runtime in order to change the behavior of the program.

For example, when you’re unit testing your test methods look like this:

[TestMethod()] public void TestSum_Given1And1_Returns2()
Code language: C# (cs)

Here TestMethod is an attribute that tells the unit testing framework that this method is a unit test and that it should execute it.

Note: In Java attributes are called annotations.

Step 1 – Create the custom attribute

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

Basically you need to subclass the Attribute class.

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

Step 2 – Assign the attribute

using System; namespace UsingCustomAttributes { 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)

Here I’m assigning the attribute to each enum value, specifying the appropriate color. The syntax here is basically [AttributeName(parameter to constructor)]. Notice that the word “Attribute” is excluded.

Step 3 – Get the attribute value at runtime

First – Add a method to extract the attribute value

Unfortunately we have to use reflection to get the attribute value. This leads to some rather complicated looking code.

using System; using System.Collections.Generic; using System.Reflection; using System.Linq; namespace UsingCustomAttributes { 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)

Note: Getting the attribute value can be reduced to a one-liner, but for clarity I’ve written it out the long way. In case you’re interested, here’s the version with the one-liner:

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)

Then – use the attribute value

using System; using System.Collections.Generic; namespace UsingCustomAttributes { class Program { static void Main(string[] args) { Console.WriteLine("Fetching devices"); List<Device> devices = LoadDevices(); 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(); } private static List<Device> LoadDevices() { return new List<Device>() { new Device() { IPAddress="", Status = DeviceStatus.Registered }, new Device() { IPAddress="", Status = DeviceStatus.PingFailed }, new Device() { IPAddress="", Status = DeviceStatus.PortNotOpen }, new Device() { IPAddress="", Status = DeviceStatus.RegistrationFailed }, }; } } }
Code language: C# (cs)

Leave a Comment