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.
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:
Obtaining the
Typeobject is a crucial step in accessing the metadata of a type when using reflection. This can be done using methods likeAssembly.GetType()orType.GetType(). Once theTypeobject is obtained, it provides detailed information about the type, enabling the system to dynamically inspect and manipulate its members.After obtaining a
Typeobject, reflection methods likeGetField(),GetMethod(), orGetProperties()can be used to inspect its members. These methods createFieldInfo,MethodInfo, orPropertyInfoobjects, which provide details about the type’s members, including their names, types, access modifiers, and custom attributes.After obtaining
MethodInfoobjects, 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.After obtaining
FieldInfoorPropertyInfoobjects, 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.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.
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:
| Class | Description |
| AbilityController | This 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, FlameLordAbilityController | These classes are used to implement unique ability methods and customize inherited characteristics to match each hero's unique abilities. |
| IgnoreAttribute | This class is used as an attribute to mark fields so that reflection ignores them, preventing those fields from being accessed or changed through reflection. |
| Program | This 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.
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.IgnoreCaseas 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.ExactBindingwhenever 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.InvokeMemberbecause 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 likeMethodInfo.Invoke,PropertyInfo.GetValue, orFieldInfo.GetValue.
Further Discoveries
Here are some further discoveries I have gathered from various sources that may provide a deeper understanding of C# Reflection:
The
BindingFlagsenumeration is used to determines which information want to retrieved from reflection. For example, usingtypeof(<class>).GetMethods(BindingFlags.Public)will return only the methods with apublicaccess 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
nameofkeyword 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 thanabilityInstance.Cooldown.GetType().Namefor retrieving the field name.Since C# 6, the
nameofkeyword provides a more efficient alternative for obtaining field names compared to reflection. For example, usenameof(abilityInstance.Cooldown)rather thanabilityInstance.Cooldown.GetType().Nameto 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.





