In C#, attributes and reflection are powerful features that often work hand-in-hand. Attributes allow you to add declarative information to your code, while reflection enables you to inspect and manipulate that information (and other metadata about your code) at runtime.
Let's break down how to use each, and then show how they combine.
Attributes are special classes that inherit from System.Attribute. They provide a way to associate metadata or declarative information with code elements like assemblies, types (classes, structs, enums, interfaces), members (methods, properties, fields, events), and parameters. This metadata can then be queried at runtime using reflection.
System.Attribute
Create a class that inherits from System.Attribute. By convention, attribute class names end with "Attribute" (e.g., MyCustomAttribute). You can define properties or fields within your attribute class to store the information you want to associate.
MyCustomAttribute
using System; // Define a custom attribute [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] public class AuthorAttribute : Attribute { public string Name { get; } public string Version { get; set; } // Optional: can have settable properties public AuthorAttribute(string name) { Name = name; Version = "1.0"; // Default version } }
[AttributeUsage]: This attribute is used on your custom attribute class to specify:
[AttributeUsage]
AttributeTargets
Class
Method
Property
All
AllowMultiple
false
Inherited
true
Place the attribute in square brackets [] directly above the code element you want to decorate. You can omit the "Attribute" suffix when applying it (e.g., [Author("John Doe")] instead of [AuthorAttribute("John Doe")]).
[]
[Author("John Doe")]
[AuthorAttribute("John Doe")]
[Author("Alice", Version = "2.0")] [Author("Bob")] // Allowed because AllowMultiple = true public class MyClass { [Author("Charlie")] public void MyMethod() { Console.WriteLine("Inside MyMethod"); } }
Reflection is the ability of a program to examine, inspect, and modify its own structure and behavior at runtime. It allows you to:
System.Reflection
Type
Assembly
MemberInfo
MethodInfo
PropertyInfo
FieldInfo
EventInfo
ConstructorInfo
ParameterInfo
CustomAttributeData
// Option 1: Using typeof operator (compile-time known type) Type myClassType = typeof(MyClass); // Option 2: Using GetType() method (runtime instance) MyClass myInstance = new MyClass(); Type instanceType = myInstance.GetType(); // Option 3: Using Type.GetType() for a type by its fully qualified name Type stringType = Type.GetType("System.String");
Type type = typeof(MyClass); // Get all public methods MethodInfo[] publicMethods = type.GetMethods(); foreach (MethodInfo method in publicMethods) { Console.WriteLine($"Method: {method.Name}"); } // Get a specific public method by name MethodInfo myMethod = type.GetMethod("MyMethod"); if (myMethod != null) { Console.WriteLine($"Found specific method: {myMethod.Name}"); } // Get properties PropertyInfo[] properties = type.GetProperties(); foreach (PropertyInfo prop in properties) { Console.WriteLine($"Property: {prop.Name}, Type: {prop.PropertyType}"); } // Get fields FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); foreach (FieldInfo field in fields) { Console.WriteLine($"Field: {field.Name}, Type: {field.FieldType}"); }
BindingFlags: Crucial for filtering the members you want to retrieve (e.g., Public, NonPublic, Instance, Static, DeclaredOnly).
BindingFlags
Public
NonPublic
Instance
Static
DeclaredOnly
Type typeToCreate = typeof(MyClass); object instance = Activator.CreateInstance(typeToCreate); // Calls default constructor // If a constructor with parameters is needed // ConstructorInfo constructor = typeToCreate.GetConstructor(new Type[] { typeof(string) }); // object instanceWithParams = constructor.Invoke(new object[] { "hello" });
MyClass instance = new MyClass(); Type type = instance.GetType(); MethodInfo methodToInvoke = type.GetMethod("MyMethod"); if (methodToInvoke != null) { methodToInvoke.Invoke(instance, null); // Invoke MyMethod on 'instance', no parameters } // If method had parameters: // MethodInfo addMethod = type.GetMethod("Add"); // public int Add(int a, int b) // object result = addMethod.Invoke(instance, new object[] { 10, 20 }); // Console.WriteLine($"Add result: {result}");
This is where the real power lies. Reflection allows you to read the metadata that attributes provide.
using System; using System.Reflection; // Don't forget this namespace! // (Re-using AuthorAttribute and MyClass from above) public class AttributeReflectionDemo { public static void Main(string[] args) { Type myClassType = typeof(MyClass); Console.WriteLine("--- Examining MyClass Attributes ---"); // Get all AuthorAttributes applied to MyClass object[] classAttributes = myClassType.GetCustomAttributes(typeof(AuthorAttribute), inherit: false); foreach (AuthorAttribute attr in classAttributes) { Console.WriteLine($"Class Author: {attr.Name}, Version: {attr.Version}"); } Console.WriteLine("\n--- Examining MyMethod Attributes ---"); MethodInfo myMethod = myClassType.GetMethod("MyMethod"); if (myMethod != null) { // Get all AuthorAttributes applied to MyMethod object[] methodAttributes = myMethod.GetCustomAttributes(typeof(AuthorAttribute), inherit: false); foreach (AuthorAttribute attr in methodAttributes) { Console.WriteLine($"Method Author: {attr.Name}, Version: {attr.Version}"); } } else { Console.WriteLine("MyMethod not found."); } Console.WriteLine("\n--- Checking for existence of a specific attribute ---"); // Check if MyClass has *any* AuthorAttribute bool hasAuthorAttribute = myClassType.IsDefined(typeof(AuthorAttribute), inherit: false); Console.WriteLine($"MyClass has AuthorAttribute: {hasAuthorAttribute}"); // Check if MyMethod has *any* AuthorAttribute bool myMethodHasAuthorAttribute = myMethod.IsDefined(typeof(AuthorAttribute), inherit: false); Console.WriteLine($"MyMethod has AuthorAttribute: {myMethodHasAuthorAttribute}"); Console.ReadLine(); // Keep console open } }
--- Examining MyClass Attributes --- Class Author: Alice, Version: 2.0 Class Author: Bob, Version: 1.0 --- Examining MyMethod Attributes --- Method Author: Charlie, Version: 1.0 --- Checking for existence of a specific attribute --- MyClass has AuthorAttribute: True MyMethod has AuthorAttribute: True
[Required]
[Range]
[Email]
[JsonProperty]
[JsonIgnore]
[Table]
[Column]
[Key]
[HttpGet]
[HttpPost]
[Authorize]
[Fact]
[Theory]
[Test]
Reflection is generally slower than direct code execution because it involves looking up metadata at runtime. For performance-critical scenarios, avoid excessive or repeated reflection calls. Often, reflection is used during application startup or for configuration, where the performance impact is negligible. If you need high performance, consider caching reflection results or using code generation tools that leverage reflection at build time.
By understanding and effectively using attributes and reflection, you can write more flexible, extensible, and powerful C# applications.
Comments - Beta - WIP