Reflection with Generics

Vaibhav • September 11, 2025

In the previous article, we explored best practices for designing and using generics effectively. Now we turn our attention to a powerful runtime feature in C#: reflection. Reflection allows you to inspect and interact with types, members, and metadata at runtime. When combined with generics, it opens up advanced scenarios like dynamic type discovery, generic method invocation, and runtime validation. In this article, we’ll explore how reflection works with generic types, how to inspect generic parameters, and how to invoke generic methods dynamically.

What Is Reflection?

Reflection is a feature in .NET that lets you examine the structure of your code at runtime. You can inspect types, methods, properties, fields, constructors, and attributes-even if you don’t know them at compile time. This is especially useful for frameworks, libraries, and tools that need to work with unknown types dynamically.

Type type = typeof(string);
Console.WriteLine(type.FullName); // Output: System.String

This example shows how to get the Type object for string and print its full name. You can do the same for generic types and inspect their parameters, constraints, and methods.

Inspecting Generic Types

When you use reflection on a generic type, you can access its generic type definition and its type arguments. This helps you understand how the type was constructed and what types were used.

Type listType = typeof(List<int>);
Console.WriteLine(listType.IsGenericType); // True

Type genericDef = listType.GetGenericTypeDefinition();
Console.WriteLine(genericDef); // Output: System.Collections.Generic.List`1[T]

Type[] typeArgs = listType.GetGenericArguments();
Console.WriteLine(typeArgs[0]); // Output: System.Int32

This code checks if List<int> is a generic type, gets its generic definition (List<T>), and prints the actual type argument (int). This is useful when analyzing types dynamically.

Generic type definitions use backticks to indicate the number of type parameters. For example, List`1 means one type parameter.

Creating Generic Types at Runtime

You can use reflection to create instances of generic types at runtime. This is useful when you don’t know the type arguments at compile time but need to construct the type dynamically.

Type genericType = typeof(List<>);
Type constructedType = genericType.MakeGenericType(typeof(string));

object instance = Activator.CreateInstance(constructedType);
Console.WriteLine(instance.GetType()); // Output: System.Collections.Generic.List`1[System.String]

This code creates a List<string> instance dynamically. You start with the generic type definition (List<>), specify the type argument (string), and use Activator to create the instance.

Invoking Generic Methods via Reflection

Reflection also lets you invoke generic methods dynamically. This is useful when building frameworks that need to call methods with different type arguments at runtime.

public class Utility
{
    public static T Echo<T>(T input) => input;
}

MethodInfo method = typeof(Utility).GetMethod("Echo");
MethodInfo genericMethod = method.MakeGenericMethod(typeof(int));

object result = genericMethod.Invoke(null, new object[] { 42 });
Console.WriteLine(result); // Output: 42

This example finds the Echo method, constructs it with int as the type argument, and invokes it with the value 42. The result is returned as an object, which you can cast if needed.

You can use MakeGenericMethod to specialize a generic method with specific type arguments, even if the method is defined in a static class.

Inspecting Generic Constraints

If a generic type or method has constraints, you can inspect them using reflection. This helps you understand what types are allowed and what capabilities are required.

public class Repository<T> where T : class, new()
{
    public T Create() => new T();
}

Type repoType = typeof(Repository<>);
Type[] typeParams = repoType.GetGenericArguments();

foreach (var param in typeParams)
{
    var constraints = param.GetGenericParameterConstraints();
    foreach (var constraint in constraints)
    {
        Console.WriteLine(constraint); // Output: System.Object (class constraint)
    }

    var attrs = param.GenericParameterAttributes;
    Console.WriteLine(attrs); // Output: ReferenceTypeConstraint, DefaultConstructorConstraint
}

This code inspects the constraints on T in Repository<T>. It prints the constraint types and attributes like class and new(). This is useful for validation and documentation tools.

Working with Open and Closed Generic Types

An open generic type has unspecified type parameters (like List<>), while a closed generic type has specific type arguments (like List<int>). Reflection lets you distinguish between them and work with both.

Type openType = typeof(List<>);
Type closedType = typeof(List<int>);

Console.WriteLine(openType.ContainsGenericParameters);  // True
Console.WriteLine(closedType.ContainsGenericParameters); // False

This helps you decide whether a type is ready to be instantiated or needs type arguments first. You can use MakeGenericType to close an open type.

Reflection with Generic Interfaces

You can use reflection to inspect whether a type implements a generic interface, and what type arguments were used. This is useful for dependency injection, plugin systems, and dynamic dispatch.

public interface IProcessor<T>
{
    void Process(T item);
}

public class StringProcessor : IProcessor<string>
{
    public void Process(string item) => Console.WriteLine(item);
}

Type processorType = typeof(StringProcessor);
Type interfaceType = processorType.GetInterface("IProcessor`1");

Type[] args = interfaceType.GetGenericArguments();
Console.WriteLine(args[0]); // Output: System.String

This code finds the generic interface implemented by StringProcessor and prints the type argument. You can use this to route messages, resolve services, or validate implementations.

Common Pitfalls with Reflection and Generics

Reflection is powerful, but it can be tricky:

- Always check for null when using GetMethod, GetInterface, or GetGenericArguments.

- Use BindingFlags to find non-public or static members.

- Be cautious with performance-reflection is slower than direct access.

- Avoid using reflection in performance-critical paths unless cached.

Cache Type, MethodInfo, and ConstructorInfo objects when using reflection repeatedly. This improves performance and avoids redundant lookups.

Summary

Reflection with generics allows you to inspect, construct, and invoke generic types and methods at runtime. You’ve learned how to get generic type definitions, inspect type arguments, create instances dynamically, invoke generic methods, and analyze constraints. You’ve also seen how to work with open and closed types, generic interfaces, and avoid common pitfalls. Reflection is a powerful tool for building flexible, dynamic systems-but it should be used thoughtfully and efficiently. In the next article, we’ll explore Generic Algorithms-how to write reusable algorithms that work across types, and how to design them for performance, clarity, and correctness.