NashTech Blog

Exploring C# Reflection and Attributes

Table of Contents

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:

  1. Inspect Types: Get metadata about types, including properties, methods, fields, and more.
  2. Create Instances Dynamically: Create objects dynamically using type information obtained through reflection.
  3. Invoke Methods: Call methods on objects dynamically.
  4. 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 the Type object that represents the Person class.
  • We retrieved the properties and methods of the Person class using GetProperties() and GetMethods().
  • We created an instance of Person dynamically using Activator.CreateInstance.
  • We invoked the Introduce method dynamically using MethodInfo.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: Author and Description.
  • The DocumentationAttribute is applied to the Person class and its Introduce method.

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 GetCustomAttributes to retrieve the custom attributes applied to both the class and its method.
  • We displayed the Author and Description properties of the DocumentationAttribute.

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 LogMethodAttribute that stores a message.
  • We applied the LogMethodAttribute to the Add and Subtract methods of the Calculator class.
  • Using reflection, we retrieved the methods and checked if the LogMethodAttribute was 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.

Picture of Ajay Jajoo

Ajay Jajoo

Leave a Comment

Your email address will not be published. Required fields are marked *

Suggested Article

Scroll to Top