Mastering Reflection in C#: A Beginner's Guide

Erik Zhou | Mar 24, 2023 min read

In this blog post, we will explore the concept of reflection in C#, looking into its various features, use cases, and pros and cons. By the end of this post, you will have a solid understanding of reflection and how it can (or can’t) be applied in your C# projects.

Introduction to Reflection

Reflection is a feature in C# that enables you to inspect and interact with the metadata of types, objects, and assemblies during runtime. This capability allows you to create dynamic, flexible, and extensible applications, as well as perform tasks like analysis and serialization.

Some common use cases for reflection include:

  • Creating plugin-based systems
  • Implementing object-relational mapping (ORM) libraries
  • Developing code generation and analysis tools

Reflection Basics

In this section, we’ll cover the fundamentals of reflection in C#, including working with type information, inspecting assembly metadata, and interacting with members.

Type Information

The System.Type class is the primary entry point for reflection in C#. You can obtain a Type instance by calling the GetType method on an object or by using the typeof operator with a type name.

Type stringType = typeof(string);
Type objectType = new object().GetType();

Once you have a Type instance, you can access various metadata about the type, such as its name, namespace, base type, and assembly.

Console.WriteLine($"Type: {stringType.FullName}");
Console.WriteLine($"Namespace: {stringType.Namespace}");
Console.WriteLine($"Base Type: {stringType.BaseType}");
Console.WriteLine($"Assembly: {stringType.Assembly}");

Inspecting Assembly Metadata

You can also work with assembly metadata using the System.Reflection.Assembly class. To obtain an Assembly instance, you can use methods like GetExecutingAssembly, GetCallingAssembly, or Load.

Assembly executingAssembly = Assembly.GetExecutingAssembly();
Assembly callingAssembly = Assembly.GetCallingAssembly();
Assembly mscorlibAssembly = Assembly.Load("mscorlib");

Once you have an Assembly instance, you can access metadata such as the assembly name, version, or location, as well as inspect the types defined within the assembly.

Console.WriteLine($"Assembly Name: {executingAssembly.FullName}");
Console.WriteLine($"Assembly Location: {executingAssembly.Location}");

Type[] assemblyTypes = executingAssembly.GetTypes();
foreach (Type type in assemblyTypes)
{
    Console.WriteLine(type.FullName);
}

Working with Members

Reflection allows you to inspect and interact with members of a type, such as fields, properties, methods, and events. You can use methods like GetFields, GetProperties, GetMethods, and GetEvents on a Type instance to obtain information about the corresponding members.

Type stringType = typeof(string);

PropertyInfo[] properties = stringType.GetProperties();
foreach (PropertyInfo property in properties)
{
    Console.WriteLine($"Property: {property.Name}");
}

MethodInfo[] methods = stringType.GetMethods();
foreach (MethodInfo method in methods)
{
  Console.WriteLine($"Method: {method.Name}");
}

Instantiating Types

Reflection allows you to create instances of types dynamically, without knowing the specific type at compile time. To instantiate a type, you can use the Activator.CreateInstance method, passing in the desired Type instance.

Type listType = typeof(List<>);
Type stringListType = listType.MakeGenericType(typeof(string));
object stringListInstance = Activator.CreateInstance(stringListType);

In this example, we create an instance of a generic List<string> type using reflection. We first obtain the generic List<> type, then make a specific version using MakeGenericType, and finally we create an instance with Activator.CreateInstance.

Invoking Methods and Accessing Properties

Reflection enables you to invoke methods and access properties on objects dynamically. To do this, you can call the Invoke method on a MethodInfo or PropertyInfo instance, respectively.

MethodInfo toUpperMethod = typeof(string).GetMethod("ToUpper", BindingFlags.Public | BindingFlags.Instance);
string input = "hello, world!";
string result = (string)toUpperMethod.Invoke(input, null);
Console.WriteLine($"Result: {result}"); // Output: HELLO, WORLD!

In this example, we use reflection to invoke the ToUpper method on a string instance. Note that the Invoke method requires an instance of the object on which to call the method, as well as an array of arguments (which is null in this case, since ToUpper takes no arguments).

Pros and Cons of Reflection

Reflection is a powerful tool, but it comes with its own set of advantages and disadvantages. Let’s explore the pros and cons of using reflection in C#.

Pros

  1. Dynamic behavior: Reflection enables you to create dynamic, flexible, and extensible applications by allowing you to inspect and interact with types, objects, and assemblies at runtime.
  2. Code analysis: Reflection can be used to analyze code during runtime, enabling the development of tools like code generators, serializers, and dependency injection frameworks.
  3. Plug-in architectures: Reflection allows you to create plugin-based systems, where new functionality can be added by simply dropping in assemblies without recompiling the main application.

Cons

  1. Performance overhead: Reflection can introduce performance overhead, as it requires additional processing to inspect and interact with metadata. This can be especially impactful when invoking methods or accessing properties, which may be significantly slower than direct calls.
  2. Security: Reflection can potentially expose sensitive information about your application’s internal implementation, making it more susceptible to security vulnerabilities or reverse engineering.
  3. Maintainability: Using reflection can result in code that is more difficult to maintain, as the relationships between types and members may not be obvious at compile time. This also means no intellisense since everything is done in runtime.

Conclusion

Reflection is a feature in C# that enables you to look at and interact with the metadata of types, objects, and assemblies during runtime. This allows you to create dynamic, flexible, and extensible applications, as well as perform tasks like analysis and serialization.

Despite its many advantages, reflection also comes with many downsides, including performance overhead, security concerns, and reduced maintainability. It’s important to carefully consider the pros and cons of using reflection in your C#