In C# development, you often encounter square brackets applied to classes, methods, and other code elements. These are known as Attributes, which provide a powerful mechanism for adding metadata to your code. This article explores how to create and use custom attributes in C#.
What Are Attributes?
Attributes are classes that allow you to add descriptive information, or metadata, to various code elements. According to the .NET documentation, the common language runtime enables you to add descriptive declarations called attributes that annotate elements of your code, such as types, fields, methods, and properties. These attributes are stored with the .NET Framework files' metadata and can be used to describe your code to the runtime or influence application behavior at runtime.
In .NET, attributes serve various purposes including serialization, security features, and preventing JIT compiler optimization to facilitate debugging. Let's examine some standard .NET attributes before diving into creating custom ones.
Creating Custom Attributes
Beyond the built-in .NET attributes, you can define your own custom attributes. All custom attributes must derive from the Attribute class and typically have names ending with "Attribute," though the "Attribute" suffix can be omitted when using them.
using System;
namespace MetadataAttributes
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public class DescriptionAttribute : System.Attribute
{
public string DescriptionText { get; }
public DescriptionAttribute(string text)
{
DescriptionText = text;
}
}
}
In this example, we've created a DescriptionAttribute that inherits from the Attribute class. Its primary function is to add descriptive information to classes. Notice the [AttributeUsage] attribute applied to our class definition. This is itself an attribute that configures how our custom attribute behaves.
Understanding AttributeUsage
The AttributeUsage attribute has three important properties:
- validOn: Specifies the types of program elements to which the attribute can be applied. It's of type AttributeTargets, wich is an enumeration.
- AllowMultiple: A boolean value indicating whether multiple instances of the attribute can be applied to a single program element.
- Inherited: A boolean value determining whether the attribute can be inherited by derived classes.
Let's explore these properties through examples.
Exploring AllowMultiple
Consider these class definitions:
using System;
using System.Collections.Generic;
namespace MetadataAttributes
{
[DescriptionAttribute("Human")]
[CodeInfo("Base class")]
public class Human
{
public string Name { get; set; }
public int Age { get; set; }
}
}
When AllowMultiple is set to true, we can apply the DescriptionAttribute multiple times without errors. However, if AllowMultiple is false, attempting to apply the attribute multiple times will result in a compilation error.
Exploring Inheritance
Now let's look at inheritance behavior:
using System;
namespace MetadataAttributes
{
[DescriptionAttribute("Learner")]
public class Student : Human
{
public string StudentId { get; set; }
}
}
To examine how attributes are inherited, we can use reflection:
using System;
using System.Reflection;
namespace MetadataAttributes
{
class AttributeDemo
{
static void Main(string[] args)
{
Type studentType = typeof(Student);
object[] attributes = studentType.GetCustomAttributes(true);
foreach (var attr in attributes)
{
if (attr is DescriptionAttribute descAttr)
{
Console.WriteLine($"{descAttr.DescriptionText} - {attr}");
}
}
Console.WriteLine("-----------------------");
Type humanType = typeof(Human);
attributes = humanType.GetCustomAttributes(true);
foreach (var attr in attributes)
{
if (attr is DescriptionAttribute descAttr)
{
Console.WriteLine($"{descAttr.DescriptionText} - {attr}");
}
}
Console.ReadLine();
}
}
}
The inheritance behavior depends on the combination of AllowMultiple and Inherited settings:
- When Inherited is false, the attribute is not passed to derived classes.
- When Inherited is true and AllowMultiple is true, derived classes receive the attribute without overriding it.
- When Inherited is true and AllowMultiple is false, derived classes inherit the attribute but can override it.
Understanding AttributeTargets
The AttributeTargets enumeration specifies which program elements can receive an attribute. Here are the possible values:
[Flags]
public enum AttributeTargets
{
Assembly = 1, // Can be applied to an assembly
Module = 2, // Can be applied to a module
Class = 4, // Can be applied to a class
Struct = 8, // Can be applied to a struct (value type)
Enum = 16, // Can be applied to an enum
Constructor = 32, // Can be applied to a constructor
Method = 64, // Can be applied to a method
Property = 128, // Can be applied to a property
Field = 256, // Can be applied to a field
Event = 512, // Can be applied to an event
Interface = 1024, // Can be applied to an interface
Parameter = 2048, // Can be applied to a parameter
Delegate = 4096, // Can be applied to a delegate
ReturnValue = 8192, // Can be applied to a return value
GenericParameter = 16384, // Can be applied to a generic parameter
All = 32767 // Can be applied to any element
}
This enumeration allows you to precisely control where your custom attributes can be applied, ensuring they're used appropriately in you're codebase.