.NET Reflection: A Comprehensive Guide for Application Development

Category Product engineering

Reflection is a powerful feature in the .NET Framework that enables developers to obtain information about assemblies, modules, types, and their members at runtime. It allows for the dynamic creation of objects, invoking methods, and accessing fields and properties. In this blog, we will talk about the Basics of .NET Reflection. 

Understanding Reflection in .NET

At the core of reflection in .NET lies the System.Reflection namespace provides introductory classes and methods for working with assemblies, modules, types, and members. Of particular importance is the System.Type class serves as a vital component in reflection by representing types and granting access to their metadata.

The Power of Reflection
Reflection offers the ability to explore and manipulate the structure of assemblies and types, making it an invaluable tool for tasks such as
— Dynamic creation of type instances.
— Binding types to existing objects.
— Method invocation and access to fields and properties.
— Information about constructors, methods, fields, events, and properties.
— Examination and manipulation of custom attributes.
— Runtime generation of types.

The Role of Assemblies, Modules, and Types

In the .NET Framework, assemblies serve as containers for modules, which, in turn, contain various types. The common language runtime (CLR) loader loads these assemblies into application domains, ensuring proper isolation and memory management. Reflection provides objects that encapsulate assemblies, modules, and types. With reflection, developers can dynamically explore their structure and interact with their members, expanding the possibilities of their applications.

Exploring Reflection in Practice

Let’s explore the situations where reflection might be used in order to comprehend its practical uses better.

Working with Assemblies
The System.Reflection.Assembly class provides a wide range of functionalities for working with assemblies, including:
i. Defining and loading assemblies.
ii. Loading modules listed in the assembly manifest.
iii. Locating and creating instances of types within an assembly.
You can use the Assembly class to load assemblies dynamically, which is particularly useful for scenarios where the assembly is not known at compile-time, which enables the development of flexible and expandable apps.
for example, let’s say we have a project where we want to use functionality from an external assembly called “DemoLibrary.dll”

using System;
using DemoLibrary;

namespace AssemblyDemo
{
class Program
{
static void Main()
{
// Create an instance of a class from DemoLibrary
DemoClass demoObject = new DemoClass();

// Call a method from DemoLibrary
demoObject.DemoMethod();

// Access a property from DemoLibrary
string value = demoObject.DemoProperty;
}
}
}

Discovering Module Information
The System.Reflection.Module class enables us to discover information about the module itself and the types contained within it. Some of the key functionalities include:
i. Retrieving the assembly that contains the module.
ii. Accessing the classes defined within the module.
iii. Obtaining global methods or specific methods defined in the module.
By leveraging the Module class, we can programmatically investigate the structure of modules and obtain useful information about their contents.

using System;
using System.Reflection;

namespace ModuleInfoDemo
{
class Program
{
static void Main()
{
// Get the current assembly
Assembly assembly = Assembly.GetExecutingAssembly();

// Display the assembly name
Console.WriteLine($"Assembly Name: {assembly.FullName}");

// Display the modules name in the assembly
Module[] modules = assembly.GetModules();
foreach (Module module in modules)
{
Console.WriteLine($"Module Name: {module.Name}");
}
}
}
}

Understanding Constructors and Methods
The System.Reflection.ConstructorInfo and System.Reflection.MethodInfo classes provide insights into constructors and methods, respectively. These classes allow us to:
i. Retrieve information such as the name, parameters, access modifiers, and implementation details of constructors and methods.
ii. Invoke constructors and methods dynamically
By utilizing the ConstructorInfo and MethodInfo classes, we can build instances of types dynamically and call their methods at runtime, allowing for flexible and dynamic application behaviour.

using System;
using System.Reflection;

class DemoClass
{
public DemoClass(int value)
{
Console.WriteLine($"Constructor called with value: {value}");
}

public void DemoMethod(string message)
{
Console.WriteLine($"DemoMethod called with message: {message}");
}
}

class Program
{
static void Main()
{
Type type = typeof(DemoClass);

// Instantiate an object using the constructor
object instance = Activator.CreateInstance(type,new object[] {42});

// Invoke a method on the object
MethodInfo method = type.GetMethod("DemoMethod");
method.Invoke(instance, new object[] { "Hello, reflection!" });
}
}

Accessing Fields and Properties
The System.Reflection.FieldInfo and System.Reflection.PropertyInfo classes provide mechanisms for accessing and manipulating fields and properties of types. These classes allow us to:
i. Obtain information such as the name, access modifiers, and implementation details of fields and properties.
ii. Get and set field and property values dynamically.
By leveraging the FieldInfo and PropertyInfo classes, we can interact with the data stored within types programmatically, enabling powerful data-driven applications.

using System;
using System.Reflection;

class DemoClass
{
public int DemoField;
public string DemoProperty { get; set; }
}

class Program
{
static void Main()
{
DemoClass demoObject = new DemoClass();
Type type = demoObject.GetType();

// Access and set the value of a field
FieldInfo field = type.GetField("DemoField");
field.SetValue(demoObject, 42);

// Access and get the value of a property
PropertyInfo property = type.GetProperty("DemoProperty");
property.SetValue(demoObject, "Hello, reflection!");

// Display the values
Console.WriteLine($"Field : {demoObject.DemoField}");
Console.WriteLine($"Property : {property.GetValue(demoObject)}");
}
}

Working with Events
The System.Reflection.EventInfo class enables us to discover and manipulate events within types. This class allows us to:
i. Obtain information such as the name, event-handler data type, and custom attributes of events.
ii. Add or remove event handlers dynamically.
By utilizing the EventInfo class, we can programmatically subscribe to and unsubscribe from events, providing dynamic event-handling capabilities in your applications.

using System;
using System.Reflection;

class DemoClass
{
public event EventHandler DemoEvent;
public void RaiseEvent(string message)
{
DemoEvent?.Invoke(this, new DemoEventArgs(message));
}
}

class DemoEventArgs : EventArgs
{
public string Message { get; }
public DemoEventArgs(string message)
{
Message = message;
}
}

class Program
{
static void Main()
{
DemoClass demoObject = new DemoClass();
Type type = demoObject.GetType();

// Attach an event handler dynamically
EventInfo demoEvent = type.GetEvent("DemoEvent");
MethodInfo eventHandler = typeof(Program).GetMethod("DemoEventHandler", BindingFlags.Static | BindingFlags.NonPublic);
Delegate handler = Delegate.CreateDelegate(demoEvent.EventHandlerType, eventHandler);
demoEvent.AddEventHandler(demoObject, handler);

// Raise the event
demoObject.RaiseEvent("Hello, reflection!");
}

private static void DemoEventHandler(object sender, EventArgs e)
{
DemoEventArgs demoEventArgs = (DemoEventArgs)e;
Console.WriteLine($"Event raised with message: {demoEventArgs.Message}");
}
}

Understanding Parameters
The System.Reflection.ParameterInfo class provides information about parameters within methods. This class allows us to:
i. Retrieve information such as a parameter’s name, data type, and position in a method signature.
ii. Determine whether a parameter is an input or output parameter.
The ParameterInfo class is very useful when working with methods that have a varying number of parameters or when dynamically inspecting and manipulating method signatures.

using System;
using System.Reflection;

class DemoClass
{
public void DemoMethod(int value, string message)
{
Console.WriteLine($"DemoMethod called with value: {value} and message: {message}");
}
}

class Program
{
static void Main()
{
DemoClass demoObject = new DemoClass();
Type type = demoObject.GetType();

// Invoke a method with parameters dynamically
MethodInfo method = type.GetMethod("DemoMethod");
object[] parameters = new object[] { 42, "Hello, reflection!" };
method.Invoke(demoObject, parameters);
}
}

Exploring Custom Attributes
The System.Reflection.CustomAttributeData class enables us to examine custom attributes in the reflection-only context of an application domain. This class allows us to:
i. Discover information about custom attributes without creating instances of them.
ii. Examine attribute metadata and retrieve attribute values.
By leveraging the CustomAttributeData class, We can obtain insights into the attributes applied to types, methods, fields, and other code entities, improving your understanding of your application’s runtime behaviour.

using System;
using System.Reflection;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
class CustomAttribute : Attribute
{
public string Description { get; }
public CustomAttribute(string description)
{
Description = description;
}
}

[Custom("This is a custom attribute")]
class DemoClass
{
public void DemoMethod()
{
Console.WriteLine("DemoMethod called");
}
}

class Program
{
static void Main()
{
Type type = typeof(DemoClass);

// Check if the class has a custom attribute
bool hasAttribute = type.IsDefined(typeof(CustomAttribute), inherit: false);
if (hasAttribute)
{
// Retrieve the custom attribute
CustomAttribute attribute = (CustomAttribute)type.GetCustomAttribute(typeof(CustomAttribute));
string description = attribute.Description;
Console.WriteLine($"Custom Attribute Description: {description}");
}
}
}

Building Dynamic Applications with Reflection

Reflection provides new possibilities for developing dynamic and flexible applications. Reflection can be leveraged to develop robust frameworks and libraries because of its ability to dynamically create types, invoke methods, access fields, and properties, and analyse custom attributes.

1. Type Browsers
The introduction of type browsers, which allow users to select types and examine their information, is one of the special applications of reflection. We can create intuitive tools that provide users with a comprehensive view of the types available within assemblies, allowing them to make informed decisions about type usage by utilising reflection.

2. Compiler Symbol Tables
Reflection is essential in compilers for languages such as JScript, where it is utilised to construct symbol tables. Compilers can use reflection to analyse types, methods, and properties at runtime, enabling advanced language features like dynamic typing and late binding.

3. Serialization and Persistence
The System.Runtime.Serialization and System.Runtime.Remoting namespaces utilise reflection indirectly through serialisation and persistence. Reflection is used to access data, choose which fields to persist and recreate objects from serialised data. These namespaces provide strong capabilities for data exchange and distributed computing by taking advantage of reflection.

Places where we utilise Reflection

  1. Dependency Injection: Reflection is commonly used in frameworks that implement dependency injection (DI). It allows for the automatic discovery and instantiation of dependencies based on their types, facilitating loose coupling and modular design.
  2. Plugin Systems: Reflection supports the development of expandable applications using plugin architectures. The application can dynamically load and interact with external modules or plugins by utilising reflection, allowing for flexible and customizable functionality.
  3. Serialization and Deserialization: Reflection is essential in serialization frameworks like JSON or XML serialisers. It enables objects to be serialized and then reassembled into objects by utilising type information and member access.
  4. Object Mapping: Reflection is used in object mapping libraries to dynamically map data from one object to another. It enables the automatic matching of property names and values, simplifying transfer data between different object models.
  5. Metadata-driven Applications: Reflection helps developers build metadata-driven applications in which the structure and behaviour of things are dynamically specified. This method is beneficial in situations such as dynamic form generation, database mapping, and code generation based on metadata.
  6. Testing and Debugging: Reflection helps with unit testing and debugging by giving access to private members of classes, allowing developers to investigate and modify the internal state for testing or troubleshooting purposes.

Conclusion

Reflection in.NET is a versatile and powerful feature that allows developers to explore, alter, and create types and their members during runtime, by leveraging the classes and methods provided by the System.Reflection namespace, developers can create dynamic and extendable applications that adapt to changing needs. Reflection is a crucial tool in your toolbox, whether designing type browsers, building compilers, or implementing serialisation. So, harness the power of reflection to open new doors in your application development path.

Ready to embark on a transformative journey? Connect with our experts and fuel your growth today!