Learn to create custom attributes and use reflection to enable metadata-driven design and framework extensibility
Attribute Usage in C# is a metadata annotation system that allows developers to declaratively add information to code elements like classes, methods, properties, and parameters. Attributes solve the problem of needing to attach metadata to code elements without modifying their core functionality.
Custom attributes are user-defined attribute classes that extend the System.Attribute base class. They allow you to create domain-specific metadata markers that can be applied to various code elements.
// Define a custom attribute with AttributeUsage to specify valid targets[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]public class AuthorAttribute : Attribute{ public string Name { get; } public string Version { get; set; } // Constructor with required parameter public AuthorAttribute(string name) { Name = name; Version = "1.0"; }}// Apply the custom attribute to a class[Author("John Doe", Version = "2.0")]public class DataProcessor{ [Author("Jane Smith")] // Applied to method public void ProcessData() { // Method implementation }}
Reflection on attributes involves inspecting code elements at runtime to discover and utilize attribute information. This enables dynamic behavior based on metadata.
using System;using System.Reflection;public class AttributeInspector{ public void InspectAttributes() { Type type = typeof(DataProcessor); // Check for class-level attributes object[] classAttributes = type.GetCustomAttributes(true); foreach (object attr in classAttributes) { if (attr is AuthorAttribute authorAttr) { Console.WriteLine($"Class author: {authorAttr.Name}, Version: {authorAttr.Version}"); } } // Check method-level attributes MethodInfo method = type.GetMethod("ProcessData"); var methodAttributes = method.GetCustomAttributes<AuthorAttribute>(); foreach (var attr in methodAttributes) { Console.WriteLine($"Method author: {attr.Name}"); } // Conditional execution based on attributes if (method.IsDefined(typeof(AuthorAttribute), false)) { Console.WriteLine("This method has author information"); } }}// Advanced usage: Finding all types with specific attribute in assemblypublic class AttributeScanner{ public IEnumerable<Type> FindTypesWithAttribute<TAttribute>() where TAttribute : Attribute { return Assembly.GetExecutingAssembly() .GetTypes() .Where(t => t.IsDefined(typeof(TAttribute))); }}
Attributes enable extending behavior without modifying existing code, adhering to the Open/Closed Principle by allowing new features through metadata rather than code changes.
Declarative Programming (DRY Principle)
They reduce boilerplate code by allowing declarative configuration that can be reused across multiple elements, eliminating repetitive imperative code.
Framework Extensibility (Inversion of Control)
Attributes provide hooks for frameworks to discover and configure components dynamically, enabling powerful IoC containers and plugin systems.
[AttributeUsage(AttributeTargets.Class, Inherited = false)]public class NonInheritedAttribute : Attribute { }[AttributeUsage(AttributeTargets.Class, Inherited = true)]public class InheritedAttribute : Attribute { }[Inherited][NonInherited]public class BaseClass { }public class DerivedClass : BaseClass { } // DerivedClass has InheritedAttribute but NOT NonInheritedAttribute// Advanced: Conditional attribute usage based on build configuration#if DEBUG [DebugOnlyAttribute]#endifpublic class DevelopmentService { }
public class AttributeCache{ private static readonly ConcurrentDictionary<MemberInfo, object[]> _attributeCache = new(); public static T[] GetCustomAttributesCached\<T\>(MemberInfo member) where T : Attribute { return (T[])_attributeCache.GetOrAdd(member, m => m.GetCustomAttributes(typeof(T), true)); }}
Performance Note: Reflection on attributes can be expensive. Consider caching attribute lookups for frequently accessed metadata.