Reflection is a powerful feature in C# that allows programs to inspect and interact with their own structure, metadata, and code at runtime. It opens up possibilities for dynamic behavior, code generation, and performing operations based on metadata. Developer typically use reflection in scenarios like serialization, deserialization, creating dynamic proxies, or building plugins.
Attributes, on the other hand, are a mechanism in C# that allows us to attach metadata to classes, methods, properties, and other code elements. We can inspect or use this metadata at runtime, often in combination with reflection.
What is Reflection in C#?
Reflection allows us to inspect and manipulate objects at runtime. We can use reflection to:
- Inspect Types: Get metadata about types, including properties, methods, fields, and more.
- Create Instances Dynamically: Create objects dynamically using type information obtained through reflection.
- Invoke Methods: Call methods on objects dynamically.
- Access Fields and Properties: Retrieve and set values of fields and properties at runtime.
Example of Reflection in C#
Example where we use reflection to inspect a class’s metadata.
using System;
using System.Reflection;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public void Introduce()
{
Console.WriteLine($"Hi, I'm {Name}, and I'm {Age} years old.");
}
}
class Program
{
static void Main(string[] args)
{
// Get the type of the class Person
Type personType = typeof(Person);
// Get the properties of the Person class
PropertyInfo[] properties = personType.GetProperties();
Console.WriteLine("Properties:");
foreach (var property in properties)
{
Console.WriteLine(property.Name);
}
// Get the methods of the Person class
MethodInfo[] methods = personType.GetMethods();
Console.WriteLine("\nMethods:");
foreach (var method in methods)
{
Console.WriteLine(method.Name);
}
// Create an instance of Person dynamically
object personInstance = Activator.CreateInstance(personType);
PropertyInfo nameProperty = personType.GetProperty("Name");
nameProperty.SetValue(personInstance, "Alice");
PropertyInfo ageProperty = personType.GetProperty("Age");
ageProperty.SetValue(personInstance, 30);
// Call the Introduce method dynamically
MethodInfo introduceMethod = personType.GetMethod("Introduce");
introduceMethod.Invoke(personInstance, null);
}
}
Output
Properties:
Name
Age
Methods:
Equals
Finalize
GetHashCode
GetType
Introduce
ToString
...
Hi, I'm Alice, and I'm 30 years old.
In this example:
- We used
typeof(Person)to get theTypeobject that represents thePersonclass. - We retrieved the properties and methods of the
Personclass usingGetProperties()andGetMethods(). - We created an instance of
Persondynamically usingActivator.CreateInstance. - We invoked the
Introducemethod dynamically usingMethodInfo.Invoke.
Reflection enables dynamic and flexible programming, but it can have a performance overhead, so it should be used judiciously.
Understanding Attributes in C#
Attributes in C# provide a way to associate metadata with program entities such as classes, methods, properties, and fields. We often use them for things like validation, logging, or adding additional metadata to our code.
Attributes in C# are applied using square brackets [] and can be retrieved using reflection.
Creating Custom Attributes
We can define our own custom attributes by inheriting from System.Attribute. Here’s an example of how to create a simple custom attribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DocumentationAttribute : Attribute
{
public string Author { get; }
public string Description { get; }
public DocumentationAttribute(string author, string description)
{
Author = author;
Description = description;
}
}
[Documentation("Alice", "This class represents a person.")]
public class Person
{
[Documentation("Alice", "This method introduces the person.")]
public void Introduce()
{
Console.WriteLine("Hello!");
}
}
In this example:
- We define a custom attribute
DocumentationAttribute, which accepts two parameters:AuthorandDescription. - The
DocumentationAttributeis applied to thePersonclass and itsIntroducemethod.
Retrieving and Using Attributes with Reflection
Now, let’s retrieve and display the custom attribute data using reflection:
using System;
using System.Reflection;
class Program
{
static void Main(string[] args)
{
// Get the type of the Person class
Type personType = typeof(Person);
// Get custom attributes applied to the Person class
var classAttributes = personType.GetCustomAttributes(typeof(DocumentationAttribute), false);
foreach (DocumentationAttribute attr in classAttributes)
{
Console.WriteLine($"Class Author: {attr.Author}, Description: {attr.Description}");
}
// Get custom attributes applied to the Introduce method
MethodInfo methodInfo = personType.GetMethod("Introduce");
var methodAttributes = methodInfo.GetCustomAttributes(typeof(DocumentationAttribute), false);
foreach (DocumentationAttribute attr in methodAttributes)
{
Console.WriteLine($"Method Author: {attr.Author}, Description: {attr.Description}");
}
}
}
Output
Class Author: Alice, Description: This class represents a person.
Method Author: Alice, Description: This method introduces the person.
In this example:
- We used
GetCustomAttributesto retrieve the custom attributes applied to both the class and its method. - We displayed the
AuthorandDescriptionproperties of theDocumentationAttribute.
Combining Reflection and Attributes
One of the common use cases for reflection and attributes is creating a dynamic system that behaves differently based on the metadata associated with code elements. For example, we might use attributes to mark certain methods for logging, validation, or permission checking.
Let’s look at an example where we use reflection and attributes for method execution tracking:
Example: Method Logging with Attributes
[AttributeUsage(AttributeTargets.Method)]
public class LogMethodAttribute : Attribute
{
public string Message { get; }
public LogMethodAttribute(string message)
{
Message = message;
}
}
public class Calculator
{
[LogMethod("Adding two numbers")]
public int Add(int a, int b) => a + b;
[LogMethod("Subtracting two numbers")]
public int Subtract(int a, int b) => a - b;
}
class Program
{
static void Main(string[] args)
{
Type calculatorType = typeof(Calculator);
MethodInfo[] methods = calculatorType.GetMethods();
foreach (var method in methods)
{
var logAttribute = method.GetCustomAttribute<LogMethodAttribute>();
if (logAttribute != null)
{
Console.WriteLine($"Logging: {logAttribute.Message}");
// Call the method dynamically
var instance = Activator.CreateInstance(calculatorType);
var result = method.Invoke(instance, new object[] { 10, 5 });
Console.WriteLine($"Result of {method.Name}: {result}");
}
}
}
}
Output
Logging: Adding two numbers
Result of Add: 15
Logging: Subtracting two numbers
Result of Subtract: 5
In this example:
- We defined a
LogMethodAttributethat stores a message. - We applied the
LogMethodAttributeto theAddandSubtractmethods of theCalculatorclass. - Using reflection, we retrieved the methods and checked if the
LogMethodAttributewas applied. - We logged the message and invoked the method dynamically.
Conclusion
Here, we explored C# Reflection and Attributes, two powerful features that allow us to inspect and manipulate metadata at runtime. Reflection enables us to inspect types, create instances dynamically, and invoke methods. Attributes associate metadata with code elements, which can be accessed at runtime, often using reflection.
By combining these features, we can create flexible and dynamic applications that behave differently based on the metadata associated with the code. However, it’s important to be mindful of the performance implications of using reflection in performance-sensitive applications.