Nailing Down Generics
Calling Generic Methods
You write a class containing a harmless generic method:
Actually, for the purposes of this discussion, this is not one method declaration. Rather, it is an infinite number of method declarations:
void GenericMethod<int>(int input)
void GenericMethod<string>(string input)
void GenericMethod<Customer>(Customer input)
- …
Each time you make a call, the compiler decides which of the infinite methods you really meant. It makes this decision each time you call something called “GenericMethod”. It makes this decision based on the compile-time types at each call site:
The compiler knows the compile-time type of each argument, and compares this to the generic type definition in order to pick the single winning “specific” method.
Easy, right? C# 101 stuff.
Calling Generic Methods Via Reflection
Let’s try to call the same method, with the same inputs, via reflection:
MethodInfo.Invoke(…) wants you to throw an object[] at the method. It wants to take the first item of that array for the first parameter of the method, the second item for the second parameter, etc.
Easy, right? Wrong. Each call to Invoke above would throw a System.InvalidOperationException with the message:
Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.
This exception message achieves the high honor of being both 100% accurate and 10% helpful. “Late bound operation” is compiler-speak for “something that is figured out dynamically at runtime instead of statically at compile time.” The figuring out happens later than compilation. The late bound operation, the thing we’re trying to do dynamically at runtime, is the method invocation itself.
Translation: the exception message is saying, “You cannot invoke the method because it still has generic parameters.” In order to invoke it, we’re going to have to first nail down the generic parameters to specific concrete types so that they won’t be generic anymore. We asked .NET to do the impossible:
You: “Please call this method.”
.NET: “No. That is not a method. It is an infinite number of methods. Try again, and tell me which one you meant.”
Once More, With Feeling
Lets narrow things down, each time we want to call the method via reflection. Just like the compiler did for us in the original compile-time example:
This time, thankfully, we get the same output as the original plain calls to GenericMethod
Generic Test Methods
Let’s say you’re using the Fixie test framework and you have defined an [Input] attribute with an associated Fixie Convention in order to have parameterized tests. If one of your test methods is generic, Fixie faces the same problem we faced above:
Fixie calls test methods via reflection, using an object[] of inputs. In this case, the object[] has length 1, and the values come from the [Input] attributes. In order to successfully invoke the MethodInfo, Fixie must also call MethodInfo.MakeGenericMethod(…), passing in the right concrete Type, in order to get a handle on the specific, concrete version of the MethodInfo. Finally, Fixie can invoke that MethodInfo.
Thanks to Anders Forsgren, Fixie can handle tests like this one. If the generic type parameter T can be nailed down to something unambiguously specific, it will be. If a generic test is declared with multiple generic type parameters like T1, T2, etc, it’ll try to nail them all down. When there’s any ambiguity for a T, though, Fixie has to assume object
as the most specific type possible.
In short, Fixie will do the most reasonable thing possible without you ever needing to know that any of the above is happening.