mercredi 25 février 2015

It was just another day with .NET. Until I had to get generic method of a static class with a generic parameter, using reflection for serialization. Doesn't sound so bad. GetRuntimeMethod("x", new[] { type }), as usual should do the trick, or so I thought.


Now, this method keeps returning null for the following variant: public static Surrogate<T> BuildSurrogate<T>(List<T> collection).


So, a quick copy to LinqPad, and a GetRuntimeMethods run later, it seemed to have all the methods as expected. Naturally, I thought perhaps, something wasn't right with the behavior of GetRuntimeMethod, so, I whipped up a quick extension, GetRuntimeMethodEx that iterates through, and to my surprise, it failed. What? How could that fail. GetRuntimeMethods has the exact the methodInfo I need.


So, I ended up breaking up the extension into parts to understand what exactly is going on, as in the code below. And it turns out (cType != cGivenType) always ended up true.


But a quick inspection, shows they were the same 'apparent' type - List<T>. Now being utterly confused, a diff on the dump of the two typeof(List<T>), and it turns out they were not the same!


The MetadataToken of the two were exactly the same. However the RuntimeTypeHandle were different. The given type had the correct AssemblyQualifiedName, and FullName, belonging to System.Collections.Generic.List``1, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089. Great. But oddly enough, the type on the generic method, had both of them as "null"! So, basically, the List<T> is a magical type with no corresponding assembly(!?). It exists, but doesn't. How fascinating!


Here's a quick dump of the diff between the two, that's relevant.


Image of the differences


Note the GenericTypeParameters, and IsGenericTypeDefinition - They are the ones which seem to make perfect sense. The oddities aside, now how could one create a Type that matches this type on the MethodInfo? Potentially, the compiler expects a generic type of List<> with the generic parameter T - The only problem is, you can't literally make a generic type with T. The T has to be a type of something, which now invalidates the equality.



private void Main()
{
var type = typeof (List<>);
var m = typeof (Builders).GetRuntimeMethods();
var surrogateBuilder = typeof (Builders)
.GetRuntimeMethodEx("BuildSurrogate", new[] {type});
}

static class Builders
{
public static Surrogate<T> BuildSurrogate<T>(List<T> collection)
{
return new Surrogate<T>
{
Items = collection.ToArray(),
};
}

public class Surrogate<T>
{
public IEnumerable<T> Items;
}
}

public static class ReflectionExtensions
{
public static MethodInfo GetRuntimeMethodEx(
this Type type, string name, params Type[] types)
{
var m = type.GetRuntimeMethods();
var res = (m.Where(t =>
{
var n = name;
return t.Name.Equals(n);
}).FirstOrDefault(t =>
{
var px = t.GetParameters().ToArray();
var currentTypes = px.Select(p => p.ParameterType).ToArray();
if (currentTypes.Length < 1) return false;
for (var i = 0; i < types.Length; i++)
{
var cGivenType = types[i];
for (var j = 0; j < currentTypes.Length; j++)
{
var cType = currentTypes[j];
if (cType != cGivenType) return false;
}
}
return true;
}));
return res;
}
}





Aucun commentaire:

Enregistrer un commentaire