In this article, we are going to learn how to call a generic method in C# using reflection.
Generic methods are an all-time favorite of library developers for their inherent reusability and type inference benefits. But because of the need for explicit type arguments in most cases (except when type inference occurs implicitly), we can’t always make the best use of them in compile time. This is where reflection comes into play.
Let’s dive in.
Call Generic Method Directly
To begin with, let’s assume we have a library that features some caption generation routines:
public class CaptionBuilder { public string? ClassCaption<T>() { var type = typeof(T); return type.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName ?? type.Name; } // omitted other routines for the brevity }
The library ships with a CaptionBuilder
class containing a few generic routines. For now, let’s just talk about the ClassCaption
method. This generates a display name from a type argument coming as part of a generic <T>
type-placeholder.
We also have two record classes:
[DisplayName("Goods Store")] public record class GoodsStore(string? Name, string? Area); [DisplayName("Employee")] public record class Stuff(string? Name, string? Designation);
We are going to use them for our caption generation example:
var builder = new CaptionBuilder(); var caption1 = builder.ClassCaption<GoodsStore>(); var caption2 = builder.ClassCaption<Stuff>(); Assert.Equal("Goods Store", caption1); Assert.Equal("Employee", caption2);
As we see, with the help of a CaptionBuilder
instance, we get the captions for GoodsStore
and Stuff
types. For this to work, we have to call the ClassCaption
method with the desired type in the <T>
placeholder. The output rightly reflects the display names of the target classes.
Now, let’s see what happens if we try with a type variable that receives a calculated value from elsewhere:
Type ResolveType(string shortCode) { return shortCode switch { "GDS" => typeof(GoodsStore), "EMP" => typeof(Stuff), _ => throw new NotImplementedException(), }; } var type = ResolveType("GDS"); // This line does not compile // var directResult = new CaptionBuilder().ClassCaption<type>();
Obviously, we can’t pass a variable in the <T>
placeholder and hence can’t call the ClassCaption
method!
We can solve this by implementing an overload of ClassCaption
that takes a type parameter instead of a generic type argument and reuses the existing code block of ClassCaption
. But that too is not feasible for an external library.
Call Generic Method Using Reflection
Alternatively, we can implement a non-generic wrapper of the CaptionBuilder
routines with the help of reflection:
public class NonGenericCaptionBuilder { private readonly CaptionBuilder _captionBuilder = new(); public string? ClassCaption(Type type) { var baseMethod = typeof(CaptionBuilder) .GetMethod(nameof(CaptionBuilder.ClassCaption))!; var genericMethod = baseMethod.MakeGenericMethod(type)!; return (string?)genericMethod.Invoke(_captionBuilder, Array.Empty<object>()); } }
So, we come up with a NonGenericCaptionBuilder
class. Inside it, we start with a readonly instance of CaptionBuilder
. As part of our non-generic routines, we are going to delegate all invocations to this _captionBuilder
instance.
Right next to it, we implement the non-generic ClassCaption
method. Inside this routine, we first extract the MethodInfo
of the original ClassCaption
method. Then, we construct a generic version of it by calling the MakeGenericMethod()
with the target type. To invoke this generic method, we need two things: an instance of the owner class of the method and the necessary arguments for the parameters of the original method in form of an object array. As we’re dealing with a parameter-less method, in our case, _captionBuilder
and an empty array does the job.
Now, we’re able to generate a caption from a type variable:
var type = ResolveType("GDS"); // The line below does not compile // var directResult = new CaptionBuilder().ClassCaption<type>(); var reflectedResult = new NonGenericCaptionBuilder().ClassCaption(type); Assert.Equal("Goods Store", reflectedResult);
Static Method
CaptionBuilder
class also has a static generic method:
public static string? StaticCaption<T>() => typeof(T).Name.ToUpper();
If we want a non-generic version of this, we need a slight change from the previous one:
public static string? StaticCaption(Type type) { var baseMethod = typeof(CaptionBuilder) .GetMethod(nameof(CaptionBuilder.StaticCaption))!; var genericMethod = baseMethod.MakeGenericMethod(type)!; return (string?)genericMethod.Invoke(null, Array.Empty<object>())!; }
During the invocation at the end, we need to pass null
instead of the _captionBuilder
instance. This is because a static method does not belong to a specific instance.
As we try this non-generic StaticCaption
method:
var type = ResolveType("GDS"); var reflectedResult = NonGenericCaptionBuilder.StaticCaption(type); Assert.Equal("GOODSSTORE", reflectedResult);
We get the result we expect.
Multiple Generic Type Arguments
No matter how many generic types a method signature has:
public string? ParentChildCaption<TParent, TChild>() { var caption1 = ClassCaption<TParent>(); var caption2 = ClassCaption<TChild>(); return $"{caption2} of {caption1}"; }
We can manage its non-generic invocation:
public string? ParentChildCaption(Type parentType, Type childType) { var baseMethod = typeof(CaptionBuilder) .GetMethod(nameof(CaptionBuilder.ParentChildCaption))!; var genericMethod = baseMethod.MakeGenericMethod(parentType, childType)!; return (string?)genericMethod.Invoke(_captionBuilder, Array.Empty<object>()); }
All we need is to call the MakeGenericMethod()
with the necessary types. And of course, we have to pass them in the appropriate order:
var type1 = ResolveType("GDS"); var type2 = ResolveType("EMP"); var reflectedResult = new NonGenericCaptionBuilder().ParentChildCaption(type1, type2); Assert.Equal("Employee of Goods Store", reflectedResult);
We can see that our non-generic version rightly produces the expected outcome.
Call Generic Method With Parameters
We may also have a generic method with generic parameters:
public string? ObjectCaption<T>(T obj) { if (obj is null) return null; if (obj.GetType().GetProperty("Name") is { } nameProperty) return nameProperty.GetValue(obj)?.ToString(); return obj?.ToString(); }
So, we aim for a non-generic version that can be called with dynamic object arguments:
public string? ObjectCaption(object obj) { if (obj is null) return null; var baseMethod = typeof(CaptionBuilder) .GetMethod(nameof(CaptionBuilder.ObjectCaption))!; var genericMethod = baseMethod.MakeGenericMethod(obj.GetType())!; return (string?)genericMethod.Invoke(_captionBuilder, new object[] { obj })!; }
This is no different than the previous ones except that we have to supply the arguments in the final invocation part.
When we try this method on a GoodsStore
object:
var store = new GoodsStore("Apex", "Street 12"); var directResult = new CaptionBuilder().ObjectCaption(store); var reflectedResult = new NonGenericCaptionBuilder().ObjectCaption(store); Assert.Equal("Apex", directResult); Assert.Equal(directResult, reflectedResult);
We get the same result as we would with the generic version.
Overloaded Methods
Now, let’s think about overloads of a generic method:
public string? ComboCaption<T1, T2>(T1 item1, T2 item2) { var caption1 = ObjectCaption(item1); var caption2 = ObjectCaption(item2); return $"{caption1} ({caption2})"; } public string? ComboCaption<T1, T2>(T1 item1, T2 item2, bool capitalize) { var caption = ComboCaption(item1, item2); return capitalize ? caption?.ToUpper() : caption; }
Here, we have two versions of ComboCaption
method with a different number of parameters.
If we want to invoke the one with three parameters only, we can do that too:
public string? ComboCaption(object item1, object item2, bool capitalize) { var baseMethod = typeof(CaptionBuilder) .GetMethods() .Single(m => m.Name == nameof(CaptionBuilder.ComboCaption) && m.GetParameters().Length == 3); var genericMethod = baseMethod.MakeGenericMethod(item1.GetType(), item2.GetType())!; return (string?)genericMethod.Invoke(_captionBuilder, new object[] { item1, item2, capitalize })!; }
The difference we see here is actually not related to the generic types but the resolution of the specific overload. This resolution part varies depending on the method signature we want to deal with. In our case, we just look for a “ComboCaption” method with three parameters. The rest of the method is pretty similar to the previous ones.
Calling of this non-generic version with sample objects:
var store = new GoodsStore("Apex", "Street 12"); var stuff = new Stuff("John", "Manager"); var directResult = new CaptionBuilder().ComboCaption(stuff, store, true); var reflectedResult = new NonGenericCaptionBuilder().ComboCaption(stuff, store, true); Assert.Equal("JOHN (APEX)", directResult); Assert.Equal(directResult, reflectedResult);
Eventually invokes the second generic ComboCaption
method and produces the desired output.
Caveats of Reflecting Generic Method
Like other reflection features, dealing with a generic method is also prone to hidden breakages and runtime errors.
One such common problem occurs due to the generic type constraint:
public string? RestrictedCaption<T>() where T : IFormattable { return typeof(T).Name; }
The IFormattable
restriction, in compile-time, prevents us from calling this routine with an unsupported type.
Sadly, we can not implement this constraint in a non-generic version:
public string? RestrictedCaption(Type type) { var baseMethod = typeof(CaptionBuilder) .GetMethod(nameof(CaptionBuilder.RestrictedCaption))!; var genericMethod = baseMethod.MakeGenericMethod(type)!; return (string?)genericMethod.Invoke(_captionBuilder, Array.Empty<object>()); }
As we see, no constraint here to prevent it from calling with non-supported types.
As a result, if we unknowingly apply it on GoodsStore
which does not support IFormattable
:
var type = typeof(GoodsStore); Assert.ThrowsAny<ArgumentException>(() => new NonGenericCaptionBuilder().RestrictedCaption(type));
We fail for an exception.
What we can do at best is to throw a friendly exception to convey the proper failure message:
public string? ImprovedRestrictedCaption(Type type) { if (!typeof(IFormattable).IsAssignableFrom(type)) throw new NotSupportedException($"{type.Name} does not implement IFormattable interface"); var baseMethod = typeof(CaptionBuilder) .GetMethod(nameof(CaptionBuilder.RestrictedCaption))!; var genericMethod = baseMethod.MakeGenericMethod(type)!; return (string?)genericMethod.Invoke(_captionBuilder, Array.Empty<object>()); }
Despite these risks, dynamic invocation of a generic method is surely a strong feature of reflection.
Conclusion
In this post, we have learned how to call a generic method using reflection. We also come to know the risk associated with such invocations.