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()
{
DemoClass demoObject = new DemoClass();
demoObject.DemoMethod();
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()
{
Assembly assembly = Assembly.GetExecutingAssembly();
Console.WriteLine($"Assembly Name: {assembly.FullName}");
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);
object instance = Activator.CreateInstance(type,new object[] {42});
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();
FieldInfo field = type.GetField("DemoField");
field.SetValue(demoObject, 42);
PropertyInfo property = type.GetProperty("DemoProperty");
property.SetValue(demoObject, "Hello, reflection!");
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();
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);
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();
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);
bool hasAttribute = type.IsDefined(typeof(CustomAttribute), inherit: false);
if (hasAttribute)
{
CustomAttribute attribute = (CustomAttribute)type.GetCustomAttribute(typeof(CustomAttribute));
string description = attribute.Description;
Console.WriteLine($"Custom Attribute Description: {description}");
}
}
}