Introduction to Reflection

Discover how to use reflection in C# to inspect and obtain metadata about types, members, and assemblies at runtime.

Every application is composed of classes and other types, along with their methods, properties, and indexers. Reflection lets us obtain type information while the program is running. For instance, we could discover what methods a class has, what properties it operates, and what interfaces it implements. Reflection is type discovery at runtime.

The reflective functionality of .NET is located in the System.Reflection namespace. To obtain information on a type, we use the System.Type class. This class has methods like:

  • GetMembers(): This returns a MemberInfo[] object.

  • GetConstructors(): This returns a ConstructorInfo[] object.

  • GetMethods(): This returns information on MethodInfo[] methods.

While the Type class exposes many members, these methods provide a general idea of its capabilities.

Obtain type information

To obtain information on a type, we must first get an instance of the Type class representing that type. We can do that in three ways:

  1. Use the typeof operator.

  2. Call GetType() on an instance of the type we are interested in.

  3. Call the static Type.GetType() method and provide the fully qualified name of the type.

The following code shows all three ways to obtain type information:

C# 14.0
// 1st approach
Type typeInfoOnInteger = typeof(int);
Console.WriteLine(typeInfoOnInteger);
// 2nd approach
var stringObject = "Hello World!";
Type typeInfoOnString = stringObject.GetType();
Console.WriteLine(typeInfoOnString);
// 3rd approach
Type? typeInfoOnBoolean = Type.GetType("System.Boolean");
Console.WriteLine(typeInfoOnBoolean);
  • Line 2: We use the typeof operator to get the type information of the built-in int type.

  • Line 7: We call the GetType() method on an instantiated string object to retrieve its type.

  • Line 11: We use the static Type.GetType() method, passing the fully qualified name of the type as a string. We use the nullable Type? because this method returns null if the requested type cannot be found.

Properties of the Type class

The Type class has properties that allow us to learn more about the type:

  • Name: The type’s name.

  • Namespace: The namespace where the type is defined.

  • BaseType: A parent type this type inherits from.

  • IsArray: This returns true if the type is an array type.

  • IsClass: This returns true if the type is a class.

  • IsEnum: This returns true if the type is an enumeration.

  • IsInterface: This returns true if the type is an interface.

  • IsValueType: This returns true if the type is a struct.

Now, let’s explore the System.String type by accessing the Type object’s properties:

C# 14.0
Type typeInfoOnString = typeof(string);
Console.WriteLine($"Name: {typeInfoOnString.Name}.");
Console.WriteLine($"Full name: {typeInfoOnString.FullName}.");
Console.WriteLine($"Namespace: {typeInfoOnString.Namespace}.");
Console.WriteLine($"Base type: {typeInfoOnString.BaseType}.");
Console.WriteLine($"Is class: {typeInfoOnString.IsClass}.");
  • Line 1: We obtain the Type object for the string class.

  • Lines 3–7: We access various properties of the Type object to print the string’s name, full name, namespace, base type, and whether it is a class.

The information shown in the output of the code above can be obtained on user-defined and third-party types as well.

Additional classes

The System.Reflection namespace also contains the following set of classes:

  • Assembly: This represents the program assembly and allows developers to interact with it.

  • AssemblyName: This contains information on the assembly.

  • MemberInfo: This is a base abstract class for types that contain information on a method, property, event, and field. For each member, there is a corresponding sub-class (MethodInfo, PropertyInfo, FieldInfo).

  • ConstructorInfo: This contains information about a constructor.

These classes represent the basic building blocks of any .NET application. Compiling a project produces an assembly containing types, which are further composed of members.

Let’s inspect the string class further and learn more about the assembly it is located in:

C# 14.0
using System.Reflection;
Type typeInfoOnString = typeof(string);
Assembly stringAssembly = typeInfoOnString.Assembly;
AssemblyName stringAssemblyName = stringAssembly.GetName();
Console.WriteLine($"Assembly name: {stringAssemblyName.Name}");
Console.WriteLine($"Assembly version: {stringAssemblyName.Version}");
  • Line 1: We import the System.Reflection namespace, which provides classes for inspecting assemblies and metadata.

  • Line 3: We get the Type information for the string class.

  • Line 5: We retrieve the Assembly object where the string type is defined.

  • Line 6: We get the AssemblyName object, which contains detailed metadata about the assembly.

  • Lines 8–9: We print the assembly’s name and version to the console.

We can also explore our own project. Even when using top-level statements, the compiler implicitly generates an internal Program class for us behind the scenes, which we can inspect:

C# 14.0
using System.Reflection;
// The Program class is implicitly generated by top-level statements
Type typeInfoOnProgram = typeof(Program);
Assembly programAssembly = typeInfoOnProgram.Assembly;
AssemblyName programAssemblyName = programAssembly.GetName();
Console.WriteLine($"Assembly name: {programAssemblyName.Name}");
Console.WriteLine($"Assembly version: {programAssemblyName.Version}");
  • Line 4: We obtain the Type information for our auto-generated Program class.

  • Lines 6–7: We retrieve the Assembly and AssemblyName objects associated with our current executing project.

  • Lines 9–10: We output the name and version of our custom assembly.