Forewarning: this answer describes a technique that probably isn't the best choice based on your example code, but it's useful to know for similar situations.
If you want to bind to the appropriate method based on the runtime type, C# 4.0+ has facilities to do just that (the dynamic
type). In your case, you want to bind the type argument T
based on the first argument, so simply pass a value typed as dynamic
as the first argument:
class Program
{
static void Main(string[] args)
{
dynamic objArr = new object[0];
dynamic intArr = new int[0];
int[] typedIntArr = new int[0];
string arrS = "[1,2]";
Serialize(objArr, arrS); // dynamic call site
Serialize(intArr, arrS); // dynamic call site
Serialize(typedIntArr, arrS); // regular static call
}
public static object Serialize<T>(T targetFieldForSerialization, string value)
{
Console.WriteLine("Type: " + typeof(T).Name);
return value.FromJson<T>();
}
}
When Serialize
is called with a dynamic argument, the compiler will emit a dynamic call site. Now, what does that mean?
A dynamic call site evaluates a call and binds to the appropriate method based on the runtime types of the arguments (and possibly the expected return type). When the call gets made, the binder will take a look at the arguments, check their actual types, and then determine which method to call and, in the case of generic methods, which type arguments to pass. The results are evident in the output from my code snippet above:
Type: Object[]
Type: Int32[]
Type: Int32[]
You may think that this sounds like a non-trivial operation, and it is. The binder has to apply standard C# binding rules to resolve the correct method. It often cannot even know all of the possible candidate methods to consider until it knows all of the runtime types involved. Fortunately, dynamic call sites in .NET typically don't go through this whole process for every call. Dynamic call sites remember the details of past invocations: when the call happens, the call site will check the current argument types against past combinations of argument types, and if it finds a match, it will call the same method it called before (with the same generic type arguments). Both these checks and the target method call get compiled, which helps with performance, and you may benefit from JIT optimizations.
Now, how effective is the call site's caching (it's "memory")? That depends on how often the argument types change, and how many different combinations it encounters throughout its lifetime. The dynamic language runtime utilizes three levels of caching, so in most cases you get pretty respectable performance--not quite what you would get with static typing, but probably better than using reflection on every call. In most cases, the call site will end up constructing rules that, if you were to code them yourself, would look something like this:
__Serialize(/* object[] */ objArr, arrS);
__Serialize(/* int[] */ intArr, arrS);
Serialize(typedIntArr, arrS);
...
private static object __Serialize(object arg1, string arg2) {
// These rules get updated as the type of 'arg1' changes:
if (arg1 is object[]) {
return Serialize<object[]>((object[])arg1, arg2);
}
else if (arg1 is int[]) {
return Serialize<int[]>((int[])arg1, arg2);
}
else {
// 1) Figure out the right method to call based on the type of 'arg1'
// 2) Update this set of rules
// 3) Call the newly bound method and return its result
}
}
So, this has all been fascinating, but is this your best option here? Based on the example code in your question, probably not. Why? In your example, it looks like the only reason you have the TType
generic parameter is so that you can capture the corresponding Type
and use it for reflection (or, rather, so DataContractJsonSerializer
can use it for reflection). The most straightforward way of doing this is, of course, to just call GetType()
on the argument, which is why Scott's second example is an ideal solution for this particular case. There's no sense in wasting the overhead of dynamic binding if you don't actually need it (note that he removed the generic parameter entirely). At some point, however, you may find yourself in a similar situation where you really could benefit from dynamic binding, and when that time comes, this information should prove useful.