简体   繁体   中英

Generics Fun: Where typeof(List<T>) != typeof(List<T>), and using Reflection to get a generic method, with generic parameters

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.

形象的差异

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;
        }
    }

That's because your type is a GenericTypeDefinition ( List<> ) and the one taken from reflecting your class is an actual List<T> .

Your code is not readable, so I wrote my own from scratch. The important part is in TypesMatch method.

public static MethodInfo GetRuntimeMethodEx(
        this Type type, string name, params Type[] types)
{
    var withMatchingParamTypes =
        from m in type.GetRuntimeMethods()
        where m.Name == name
        let parameterTypes = m.GetParameters().Select(p => p.ParameterType).ToArray()
        where parameterTypes.Length == types.Length
        let pairs = parameterTypes.Zip(types, (actual, expected) => new {actual, expected})
        where pairs.All(x => TypesMatch(x.actual, x.expected))
        select m;

    return withMatchingParamTypes.FirstOrDefault();
}

private static bool TypesMatch(Type actual, Type expected)
{
    if (actual == expected)
        return true;

    if (actual.IsGenericType && expected.IsGenericTypeDefinition)
        return actual.GetGenericTypeDefinition() == expected;

    return false;
}

Returns your method, as expected.

You can't create a Type instance that represents List<T> with unknown T . That's what GetGenericTypeDefinition and List<> are for.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM