DEV Community

yogini16
yogini16

Posted on • Edited on

Reflection in c#

Use of Reflection

There are multiple places / scenarios where we can make use of reflection. It allows us to inspect and manipulate the metadata of types, objects and assemblies at runtime.

Below are some common scenarios

1. Dynamic loading of types and assemblies: Reflection allows you to load types and assemblies at runtime dynamically, which can be useful when you need to load a type that is not known at compile-time or when you want to load an assembly based on user input or configuration settings.

2. Object instantiation and manipulation: Reflection allows you to create instances of types and manipulate their properties, fields, and methods at runtime, which can be useful for scenarios such as dependency injection, serialization, or dynamic configuration.

3. Attribute-based programming: Reflection allows you to inspect and apply custom attributes to types, methods, properties, fields, and other members of a class. Attributes can be used for various purposes, such as metadata annotations, validation, or code generation.

4. Code generation and compilation: Reflection allows you to dynamically generate and compile code at runtime, which can be useful for scenarios such as dynamic proxy generation, dynamic code generation for performance optimization, or dynamic code generation for interop scenarios.

5. Debugging and testing: Reflection can be used for debugging and testing purposes, such as inspecting the state of an object or checking the correctness of code annotations or attributes.

6. Dynamic loading of plugins and modules: Reflection allows you to discover and load plugins or modules at runtime dynamically, which can be useful for extensibility scenarios where you want to allow users to extend or customize your application without recompiling it.

With that, Reflection is a powerful feature in C# that allows us to inspect and manipulate the metadata of types, objects, and assemblies at runtime, and it can be used in a variety of scenarios to add flexibility, extensibility, and dynamism to the code.

Few uses that I really liked as below

1. Accessing Private methods from same/ different assembly

As we all know, in C#, private methods are inaccessible from outside the class in which they are defined, even if they are defined in the same assembly.
This is by design, as private methods are intended to be internal implementation details of a class and should not be accessed directly by other code.

However, there are ways to access private methods from a different assembly using reflection. Reflection allows you to inspect and manipulate the metadata of an assembly at runtime, including the private members of a class.

Below is an example for the same

Suppose we have the following class in a assembly called CalculatorLibrary.dll:


namespace CalculatorLibrary
{
    public class CalculatorClass
    {
        private void AdditionPrivateMethod()
        {
            // Implementation details here
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

As here, AdditionPrivateMethod is the private method, we can use reflection to get the MethodInfo object that represents the method and then invoke it.

See below code:

using System;
using System.Reflection;

namespace MyOtherAssembly
{
    class Program
    {
        static void Main(string[] args)
        {
            // Load the assembly containing CalculatorClass
            Assembly assembly = Assembly.LoadFrom("CalculatorLibrary.dll");

            // Get the type of CalculatorClass
            Type calculatorClassType = assembly.GetType("CalculatorLibrary.CalculatorClass");

            // Create an instance of CalculatorClass
            object calculatorClassInstance = Activator.CreateInstance(calculatorClassType);

            // Get the private method AdditionPrivateMethod 
            MethodInfo additionPrivateMethod = calculatorClassType.GetMethod("AdditionPrivateMethod ", BindingFlags.NonPublic | BindingFlags.Instance);

            // Invoke the private method on the instance of CalculatorClass
            additionPrivateMethod .Invoke(calculatorClassInstance, null);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

In this example, we use Assembly.LoadFrom to load the CalculatorLibrary assembly, then we get the Type object that represents CalculatorClass. We create an instance of CalculatorClass using Activator.CreateInstance, and then use Type.GetMethod with the BindingFlags.NonPublic and BindingFlags.Instance flags to get the MethodInfo object that represents the AdditionPrivateMethod method. Finally, we invoke the private method on the instance of CalculatorClass using MethodInfo.Invoke.

Note that accessing private methods from another assembly using reflection can be risky and should be used with caution. It can make your code more difficult to understand, maintain and debug, and it may also break if the implementation of the private method changes in a future version of the assembly. Therefore, it's generally recommended to avoid accessing private methods from other assemblies if possible, and instead use public or protected methods to expose the functionality you need.

Another favorite example is

Dependency Injection

**Reflection **is a key aspect of dependency injection, as it allows for the dynamic discovery and instantiation of objects and their dependencies at runtime.

In C#, dependency injection is typically achieved through the use of inversion of control (IoC) containers, which are frameworks that manage the creation and resolution of objects and their dependencies. IoC containers use reflection to scan the codebase for classes and their dependencies, and then create and inject these dependencies as needed.

Below is the example of constructor injection

public class MyService : IService
{
    private readonly IRepository _repository;

    public MyService(IRepository repository)
    {
        _repository = repository;
    }

    public void DoSomething()
    {
        // Do something with the repository
    }
}

Enter fullscreen mode Exit fullscreen mode

In this code, the MyService class has a dependency on the IRepository interface, which is injected through the constructor. In order to instantiate MyService and provide it with a concrete implementation of IRepository, an IoC container can be used.

Here's an example of how this might be done using the Autofac IoC container:


var builder = new ContainerBuilder();

// Register the implementation of IRepository
builder.RegisterType<MyRepository>().As<IRepository>();

// Register the implementation of IService, and specify the constructor parameter
builder.RegisterType<MyService>()
    .As<IService>()
    .WithParameter(
        new ResolvedParameter(
            (pi, ctx) => pi.ParameterType == typeof(IRepository),
            (pi, ctx) => ctx.Resolve<IRepository>()
        )
    );

// Build the container
var container = builder.Build();

// Resolve an instance of IService
var service = container.Resolve<IService>();

Enter fullscreen mode Exit fullscreen mode

In above example, MyRepository is registered as the implementation of IRepository, and MyService is registered as the implementation of IService. When IService is resolved from the container, Autofac will automatically inject MyRepository into the MyService constructor using reflection to identify the parameter type.

Reflection is used here to dynamically discover the types of the constructor parameters, and to resolve the appropriate concrete implementations at runtime. This allows for flexible and modular code, and makes it easy to swap out dependencies without modifying the consuming code.

Same way reflection can be used in Factory and Abstract Factory design pattern with dynamic object creation.

Top comments (0)