Introduction
C# generics require type parameters to be known at compile time (List<int>, Dictionary<string, object>), but sometimes you have a System.Type variable at runtime and need to use it as a generic parameter. This is not directly supported by the language — you cannot write List<myType> when myType is a variable. The solution is to use reflection via Type.MakeGenericType() and Activator.CreateInstance(), or to redesign with non-generic interfaces and the strategy pattern.
The Problem
1Type myType = typeof(string); // Known only at runtime
2
3// This does NOT compile — T must be a compile-time type
4// var list = new List<myType>();
5
6// What we want: create List<string> from the Type variable
Generics in C# are resolved at compile time. The compiler generates specialized code for each T, so it cannot accept a runtime Type variable.
Solution 1: MakeGenericType + Reflection
1using System;
2using System.Collections;
3
4Type elementType = typeof(int); // Could come from runtime logic
5
6// Create the generic type List<int> at runtime
7Type listType = typeof(List<>).MakeGenericType(elementType);
8
9// Create an instance
10object list = Activator.CreateInstance(listType);
11
12// Call methods via reflection
13var addMethod = listType.GetMethod("Add");
14addMethod.Invoke(list, new object[] { 42 });
15addMethod.Invoke(list, new object[] { 100 });
16
17// Access via non-generic interface
18var enumerable = (IEnumerable)list;
19foreach (var item in enumerable)
20{
21 Console.WriteLine(item); // 42, 100
22}
23
24// Get count
25var countProp = listType.GetProperty("Count");
26Console.WriteLine($"Count: {countProp.GetValue(list)}"); // 2
typeof(List<>) is the open generic type. MakeGenericType(elementType) closes it with the runtime type.
Solution 2: Generic Method via Reflection
1public class Factory
2{
3 public static T CreateDefault<T>() where T : new()
4 {
5 return new T();
6 }
7}
8
9// Call CreateDefault<SomeType>() where SomeType is a runtime Type
10Type targetType = typeof(MyClass);
11
12var method = typeof(Factory).GetMethod("CreateDefault");
13var genericMethod = method.MakeGenericMethod(targetType);
14var instance = genericMethod.Invoke(null, null);
15
16Console.WriteLine(instance.GetType()); // MyClass
MakeGenericMethod is the method equivalent of MakeGenericType — it creates a closed generic method from an open one.
Solution 3: Dynamic Dispatch with Interfaces
Design your code with a non-generic interface so callers do not need generics:
1// Non-generic interface
2public interface IRepository
3{
4 object GetById(int id);
5 void Save(object entity);
6}
7
8// Generic implementation
9public class Repository<T> : IRepository where T : class
10{
11 public object GetById(int id) => /* ... */ default(T);
12 public void Save(object entity) => Save((T)entity);
13 private void Save(T entity) { /* typed save logic */ }
14}
15
16// Factory that creates the right repository at runtime
17public static IRepository CreateRepository(Type entityType)
18{
19 Type repoType = typeof(Repository<>).MakeGenericType(entityType);
20 return (IRepository)Activator.CreateInstance(repoType);
21}
22
23// Usage
24Type modelType = typeof(Customer);
25IRepository repo = CreateRepository(modelType);
26repo.Save(new Customer { Name = "Alice" });
Solution 4: Dictionary of Type-Specific Actions
1public class TypeDispatcher
2{
3 private readonly Dictionary<Type, Action<object>> _handlers = new();
4
5 public void Register<T>(Action<T> handler)
6 {
7 _handlers[typeof(T)] = obj => handler((T)obj);
8 }
9
10 public void Dispatch(object value)
11 {
12 Type type = value.GetType();
13 if (_handlers.TryGetValue(type, out var handler))
14 {
15 handler(value);
16 }
17 else
18 {
19 throw new InvalidOperationException($"No handler for {type}");
20 }
21 }
22}
23
24// Usage
25var dispatcher = new TypeDispatcher();
26dispatcher.Register<string>(s => Console.WriteLine($"String: {s}"));
27dispatcher.Register<int>(n => Console.WriteLine($"Int: {n}"));
28
29dispatcher.Dispatch("hello"); // String: hello
30dispatcher.Dispatch(42); // Int: 42
Solution 5: Using dynamic
1public class Processor
2{
3 public void Process<T>(T item)
4 {
5 Console.WriteLine($"Processing {typeof(T).Name}: {item}");
6 }
7}
8
9// Use dynamic to bypass compile-time generic resolution
10Type runtimeType = typeof(string);
11var processor = new Processor();
12
13dynamic value = Convert.ChangeType("hello", runtimeType);
14processor.Process(value); // Compiler resolves at runtime via DLR
15// Output: Processing String: hello
dynamic defers type resolution to runtime, letting the DLR dispatch to the correct generic overload.
For hot paths where reflection overhead matters:
1using System.Linq.Expressions;
2
3public static class GenericFactory
4{
5 private static readonly Dictionary<Type, Func<object>> _cache = new();
6
7 public static object Create(Type type)
8 {
9 if (!_cache.TryGetValue(type, out var factory))
10 {
11 var expr = Expression.Lambda<Func<object>>(
12 Expression.Convert(
13 Expression.New(type),
14 typeof(object)
15 )
16 );
17 factory = expr.Compile();
18 _cache[type] = factory;
19 }
20 return factory();
21 }
22}
23
24// 10x faster than Activator.CreateInstance for repeated calls
25var obj = GenericFactory.Create(typeof(MyClass));
Common Pitfalls
Performance overhead: MakeGenericType and Activator.CreateInstance use reflection, which is 10-100x slower than direct construction. Cache the constructed Type and compiled delegates for hot paths.
Generic constraints not checked at compile time: If the generic class has where T : IComparable, MakeGenericType still creates the type at runtime but will throw ArgumentException if the constraint is violated.
Value types and boxing: Using object as the bridge between generic and non-generic code boxes value types. For performance-sensitive code with value types, consider source generators.
Assembly loading issues: MakeGenericType can fail if the type is in an assembly that has not been loaded yet. Ensure all referenced assemblies are loaded.
Thread safety: Shared caches of generic types or compiled expressions need ConcurrentDictionary in multi-threaded scenarios.
Summary
C# generics require compile-time type parameters — you cannot use a Type variable directly
Use typeof(Generic<>).MakeGenericType(runtimeType) to construct generic types at runtime
Use method.MakeGenericMethod(runtimeType) for generic method invocation
Design non-generic interfaces (IRepository) to avoid runtime generics in calling code
Use dynamic for simple cases where DLR dispatch is acceptable
Cache constructed types and compiled expressions to avoid repeated reflection overhead