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: