Skip to main content

Command Palette

Search for a command to run...

C# Reflection

Updated
8 min read
C# Reflection

Introduction

Hello all, today I back with another C# topic, and today I will share what i learned about C# Reflection. I hope with this article you will know more about C# Reflection and use it on your project.

What It Is?

Reflection is a powerful feature that allows programs to inspect and interact with the metadata and structure of assemblies at runtime. It provides detailed information about a type, such as the names of constructors, fields, methods, and properties of a class. Additionally, reflection enables dynamic operations, such as creating class instances, modifying field values, instantiating generic types, and invoking methods.

Metadata is detailed information about code elements, such as assemblies, methods, fields, and other parts of the code.

Why Implement It?

There are several pros and cons to consider before implement reflection in our project. Here are some of them:

Pros

  • It can be used to access private methods and members of a class, which is useful for unit testing and debugging.

  • It can be used to build flexible systems like plugins, where types can be loaded and used easily.

Cons

  • Could potentially increase code complexity.

  • It may be challenging for new programmers to understand.

  • It can pose security risks by revealing internal implementation details and sensitive data, as it can be used to access the private or protected members of a class.

  • It can impact performance, especially in performance-critical applications, due to the overhead of runtime type inspection and dynamic invocation.

How It Works?

To understand how reflection works in the system, let's see how to use it. To use reflection in the code, import the System.Reflection namespace. In this implementation, reflection is used to create an instance of a class with Activator.CreateInstance<>, retrieve field values with .GetType() and .GetField(), and invoke methods using .Invoke(), as shown in the code below:

using System.Reflection;

namespace Lncodes.Example.Reflection;

public static class Program
{
    static void Main()
    {
        var abilityInstance = Activator.CreateInstance<BaneAbilityController>();
        FieldInfo[] fieldInfoCollection = abilityInstance.GetType().GetFields();
        MethodInfo[] methodInfoCollection = abilityInstance.GetType().GetMethods();

        foreach (var item in fieldInfoCollection)
            Console.WriteLine(item.GetValue(abilityInstance));

        foreach (var item in methodInfoCollection)
            item.Invoke(abilityInstance, null);
    }
}

In the code above, reflection is used to dynamically create an instance of the BaneAbilityController class, display all its field information, and invoke all its methods. Here's what will happen in the system once implementing reflection:

  1. Obtaining the Type object is a crucial step in accessing the metadata of a type when using reflection. This can be done using methods like Assembly.GetType() or Type.GetType(). Once the Type object is obtained, it provides detailed information about the type, enabling the system to dynamically inspect and manipulate its members.

  2. After obtaining a Type object, reflection methods like GetField(), GetMethod(), or GetProperties() can be used to inspect its members. These methods create FieldInfo, MethodInfo, or PropertyInfo objects, which provide details about the type’s members, including their names, types, access modifiers, and custom attributes.

  3. After obtaining MethodInfo objects, the <info>.Invoke() method can be used to invoke the method. before invoking the method, the system verifies that the method is safe to call by ensuring the arguments are correct and that the method has the required security permissions to be invoked.

  4. After obtaining FieldInfo or PropertyInfo objects, the <info>.GetValue() method can be used to retrieve the value of the field or property. Before retrieving the value, the system checks if the field or property is static, ensures that the type of the object matches, and verifies that the access modifier allows access to retrieve the value.

  5. The Activator.CreateInstance() method can be used to dynamically create instances of types. Before creating an instance, the system checks if the type can be instantiated, selects the appropriate constructor, and verifies that the constructor is suitable for creating the instance.

The Type.InvokeMember() method provides an alternative way to interact with type members by name, allowing operations like getting values, invoking methods, and creating instances. However, using MethodInfo, FieldInfo, or PropertyInfo offer a more precise and type-safe approach.

Console Application

In my console application, I use reflection to create instances, retrieve field information, invoke methods, and check if a field has a specific attribute in the ability hero classes. Each hero ability classes has unique field information and unique methods name and implementation.

Below is a list of the classes I used to create this console application, along with a brief explanation of each class:

ClassDescription
AbilityControllerThis class is used as the parent class for all hero ability classes. It provides fundamental fields related to the ability's characteristics that can be adjusted by each derived class to meet the specific needs of its hero's abilities.
AbaddonAbilityController, BaneAbilityController, FlameLordAbilityControllerThese classes are used to implement unique ability methods and customize inherited characteristics to match each hero's unique abilities.
IgnoreAttributeThis class is used as an attribute to mark fields so that reflection ignores them, preventing those fields from being accessed or changed through reflection.
ProgramThis class is used to display information about hero ability classes. It shows the class name, field names along with their values, any fields with attributes, and method names along with their outputs.

In the video above, the console application displays detailed information for each hero ability class. It shows the class name, field names with their values, and method names with their outputs. Additionally, fields marked with an 'Ignore' attribute will have their values hidden. All this information is gathered using reflection at runtime.

By using reflection, inspecting and interacting with type metadata at runtime becomes easier. Reflection provides access to the type information such as class names, field names, and method names, even when the types are not known at compile time. Reflection makes the code more adaptable to a variety of types encountered during runtime.

The source code for this console application can be viewed and downloaded at Project Repository – Github.

Additional Information

I have discovered some additional information about C# Reflection. If you have any additional information regarding C# Reflection that you'd like to share, please feel free to leave a comment.

Reflection Performance Tips

Reflection can significantly affect performance by dynamically inspecting and interacting with a type and its members at runtime. To enhance efficiency and minimize these performance costs, consider the following practical tips:

  • Use reflection only on the specific parts of a type or assembly that are needed. This helps reduce the amount of metadata the system needs to inspect and process.

  • Avoid using BindingFlags.IgnoreCase as it can potentially cause performance overhead. When this flag is used, the system needs to perform additional process by converting the member names of a type to a common letter case before comparing them.

  • Use BindingFlags.ExactBinding whenever the argument type matches the method parameter type. When this flag is used, the system will throw an error immediately if the argument types don't match, which improves performance by bypassing the type coercion process.

    Type coercion is a process that changes one data type into another. This can occur either implicitly, where the conversion happens automatically, or explicitly, where the programmer specifies the conversion.
  • Avoid using Type.InvokeMember because it is often less efficient and harder to maintain compared to specialized reflection methods. For better performance, clarity, and type safety, it's recommended to use dedicated methods like MethodInfo.Invoke, PropertyInfo.GetValue, or FieldInfo.GetValue.

Further Discoveries

Here are some further discoveries I have gathered from various sources that may provide a deeper understanding of C# Reflection:

  • The BindingFlags enumeration is used to determines which information want to retrieved from reflection. For example, using typeof(<class>).GetMethods(BindingFlags.Public) will return only the methods with a public access modifier from the specified class.

  • Reflection can make typed programming languages, such as C# or Java, behave more like dynamic languages by allowing code inspection and manipulation at runtime.

  • Use reflection wisely, as it breaks principles of typed programming languages, such as encapsulation and abstraction. It can bypass access restrictions and make the code more difficult to maintain.

  • Since C# 6, the nameof keyword offers a more efficient way to obtain names for fields, methods, properties, and other code elements compared to reflection. For example, nameof(abilityInstance.Cooldown) is better than abilityInstance.Cooldown.GetType().Name for retrieving the field name.

  • Since C# 6, the nameof keyword provides a more efficient alternative for obtaining field names compared to reflection. For example, use nameof(abilityInstance.Cooldown) rather than abilityInstance.Cooldown.GetType().Name to get the field name.

  • Use expression trees instead of reflection for tasks that involve dynamic code creation and execution. Expression trees provide better performance and type safety by allowing code to be compiled and optimized at runtime, unlike reflection, which depends on inspecting metadata at runtime.

  • Accessing metadata can increase the working set, leading to higher memory usage and more frequent memory allocations. This can result in more frequent garbage collection events to to handle the extra memory usage.

    Working set refers to the amount of memory a program is currently using. A larger working set means the program is using more memory.

Reference

  1. C# Reflection – Microsoft

  2. Binding Flags – Microsoft

  3. Deep Dive C# Reflection – Microsoft

C#

Part 3 of 7

In this series, I’ll share the most important lessons and discoveries I’ve acquired about C#. From fundamental concepts to advanced features, discover how these lessons have sharpened my coding skills and how they can benefit to my projects.

Up next

C# Delegate

Introduction Hello all, today I back with another C# topic, and today I will share what i learned about C# Delegate. I hope with this article you will know more about C# Delegate and use it on your project. What Is It? A delegate is a type that holds...

More from this blog

L

Last Night Codes

11 posts

Last Night Codes shares my journey through learning different programming languages and documenting my projects. Explore practical tips, insights, and personal coding experiences.