Author avatar

Matt Ferderer

How to Create Your Own Custom Attributes in C#

Matt Ferderer

  • Aug 15, 2019
  • 8 Min read
  • 46 Views
  • Aug 15, 2019
  • 8 Min read
  • 46 Views
Languages Frameworks and Tools
C#

Introduction

Attributes are a great way to add metadata or information in a declaritive way to your code. In .NET MVC projects with C#, you can find attributes used to add validation to models.

1
2
3
4
5
6
7
8
  [Required]
  [StringLength(50, MinimumLength = 10)]
  public string Title { get; set; }
  
  [Required]
  [Display(Name = "Article Date")]
  [DataType(DataType.Date)]
  public DateTime ArticleDate { get; set; }
csharp

Attributes help define routes, HTTP verbs, and permissions.

1
2
3
4
5
6
7
8
9
10
11
[Route("Articles")]
[Authorize(Policy = "Editor")]
public class NewsArticleController : Controller
{
  [Route("")]
  [Route("List")]
  public IActionResult Index() { ... }

  [HttpPost("article/{id}")]
  public IActionResult Edit(int id) { ... }
}
csharp

How to Create a Simple Attribute

In Visual Studio the quickest and easiest way to get started with Attributes is with the Attribute Snippet. You can type Attribute and press Tab to have Visual Studio generate a sample Attribute snippet. That Attribute snippet generates the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[System.AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
sealed class MyAttribute : Attribute
{
  // See the attribute guidelines at 
  //  http://go.microsoft.com/fwlink/?LinkId=85236
  readonly string positionalString;

  // This is a positional argument
  public MyAttribute(string positionalString)
  {
    this.positionalString = positionalString;

    // TODO: Implement code here

    throw new NotImplementedException();
  }

  public string PositionalString
  {
    get { return positionalString; }
  }

  // This is a named argument
  public int NamedInt { get; set; }
}
csharp

This generates a new class that inherits from the Attribute class. It is not required that the class ends with "Attribute" but it is a convention to help others understand your code.

An important thing to note is that the class has an attribute itself that asks three questions.

  1. Attribute Targets - What type of elements can this attribute be applied to? AttributeTargets is an enum that lists the possible targets available.
  2. Inherited - If you apply this attribute to a class named AnimalClass and you inherit another class named DogClass from AnimalClass, should this attribute be inherited as well?
  3. Allow Multiple - Can multiple instances of this attribute be set on a single element?

An Attribute class can accept required constructor parameters, optional constructor parameters, and multiple constructor overloads, just like most any other class in C#.

Using a similar example to the one in Jason Roberts' C# Attributes: Power and Flexibility for Your Code Pluralsight course, we can create an attribute that changes the color of a property when displayed in a console app.

We will modify the original snippet generated by Visual Studio to create a Color Attribute class.

1
2
3
4
5
6
7
8
9
10
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = true)]
sealed class ColorAttribute : Attribute
{
  public ColorAttribute(ConsoleColor color = ConsoleColor.Cyan)
  {
      Color = color;
  }

  public ConsoleColor Color { get; }
}
csharp

This allows someone to use [Color] or [Color(ConsoleColor.Red)] on a property. It sets the default color to be Cyan if no color is added to the attribute when used.

A common thing that trips people up with custom attributes is that they are a two step process. The above code only allows us to set the attribute on a property. We now need to add logic elsewhere in our application to use our new attribute.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// First apply our new attributes to properties in a class
public class Dog
{
  [Color(ConsoleColor.Red)]
  public string Name { get; set; }
  [Color]
  public string Breed { get; set; }
  public int Age { get; set; }
}

// Next create a class to use those properties
public static class DogConsoleWriter
{
  public static void Line(Dog dog)
  {
    var defaultColor = Console.ForegroundColor;
    Console.Write("Name: ");
    // Here the console foreground is set to either the attribute color or a default color
    Console.ForegroundColor = GetPropertyColor(nameof(Dog.Name)) ?? defaultColor; ;
    Console.Write(dog.Name);
    Console.ForegroundColor = defaultColor;
    Console.WriteLine();

    Console.Write("Breed: ");
    Console.ForegroundColor = GetPropertyColor(nameof(Dog.Breed)) ?? defaultColor;
    Console.Write(dog.Breed);
    Console.ForegroundColor = defaultColor;
    Console.WriteLine();

    Console.Write("Age: ");
    Console.ForegroundColor = GetPropertyColor(nameof(Dog.Age)) ?? defaultColor;
    Console.Write(dog.Age);
    Console.ForegroundColor = defaultColor;
    Console.WriteLine();
  }

  // Here is the most important part
  private static ConsoleColor? GetPropertyColor(string propertyName)
  {
    // This uses C#'s reflection to get the attribute if one exists
    PropertyInfo propertyInfo = typeof(Dog).GetProperty(propertyName);
    ColorAttribute colorAttribute = (ColorAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(ColorAttribute));

    // If colorAttribute is not null, than a color attribute exists
    if(colorAttribute != null)
    {
        return colorAttribute.Color;
    }
    return null;
  }
}
csharp

Now a Console application could use this class in a similar fashion:

1
2
3
4
5
DogConsoleWriter.Line(new Dog
{
  Name= "Astro",
  Breed= "Newfoundland"
});
csharp

Let's take a closer look at the GetPropertyColor method where the most important steps are done.

1
2
3
4
5
6
7
8
9
10
11
private static ConsoleColor? GetPropertyColor(string propertyName)
{
  PropertyInfo propertyInfo = typeof(Dog).GetProperty(propertyName);
  ColorAttribute colorAttribute = (ColorAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(ColorAttribute));

  if(colorAttribute != null)
  {
      return colorAttribute.Color;
  }
  return null;
}
csharp

This method accepts a string of the property name GetPropertyColor(string propertyName). The string is passed by using the nameof operator, nameof(Dog.Age), to provide type safety.

The property name is then used in the first line of the method to get the Property Info for the type specified. The type in our case is the Dog type. PropertyInfo propertyInfo = typeof(Dog).GetProperty(propertyName);

Once the PropertyInfo is obtained we can check to see if it has any attributes applied. Since we only care about the ColorAttribute, we specify that type. ColorAttribute colorAttribute = (ColorAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(ColorAttribute));

GetCustomAttribute will return null if it cannot find an attribute.

Where Attributes Can Be Applied

You can add an attribute to any of the following in C#:

  • Classes
  • Methods
  • Enums
  • Events
  • Fields
  • Properties
  • Structs
  • Parameters
  • Constructors
  • Delegates
  • Interfaces
  • Return values
  • Assemblies
  • Other Attributes too!

Summary

Adding attributes helps others to understand what your code aims to accomplish at a quick glance, without having to understand the logical steps that make it happen.

To learn even more about C# Attributes, check out the full course C# Attributes: Power and Flexibility for Your Code by Jason Roberts. Jason describes multiple attribute use case examples and even more advanced methods of creating and using attributes.

About the Author

Matt Ferderer is a software developer who tweets, posts, and blogs about web development.

2