Workaround for WP7 GetMethod reflection

WP7 currently has a bug which does not allow to call GetMethod and GetMethods if the type has a overridden generic method or the requested method is a overridden generic method. In this post I will show how to workaround this Bug.

The problem

In case a type has an overridden method as shown in the next code example GetMehtod() and GetMethods() does not return the requested methods but instead a MethodAccessException is thrown.

public abstract class BaseClassWithGenericMethod
{
    public virtual System.Collections.IList CreateList<T>()
    {
        return new List<T>();
    }
}

public class DerivedClassWithGenericMethod
    : BaseClassWithGenericMethod
{
    public override System.Collections.IList CreateList<T>()
    {
        return new List<T>();
    }
}

The workaround

This problem does not occur in case the BindingFlags.DeclaredOnly is used. This fact can be used for a workaround for this bug. Basically, all methods are collected from the type and its base types using the  BindingFlags.DeclaredOnly binding flag. The .NET 4.0 implementation of these mehtods does not return the static and private methods of base types. So we will filter them out as well by modifying the binding flags for the call on the base types.

The source code

Finally, here is the source code of the extension methods for the Type class that behave as the .NET 4.0 implementations of GetMethod and GetMethods do:

    public static class TypeExtensions
    {
        public static MethodInfo GetMethodWp7(this Type type, string name)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            return GetMethod(type, name, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance, null, CallingConventions.Any, null, null);
        }

        public static MethodInfo GetMethodWp7(this Type type, string name, Type[] types)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            if (types == null)
            {
                throw new ArgumentNullException("types");
            }

            if (types.Any(t => t == null))
            {
                throw new ArgumentNullException("types");
            }

            return GetMethod(type, name, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance, null, CallingConventions.Any, types, null);
        }

        public static MethodInfo GetMethodWp7(this Type type, string name, BindingFlags bindingAttr)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            return GetMethod(type, name, bindingAttr, null, CallingConventions.Any, null, null);
        }

        public static MethodInfo GetMethodWp7(this Type type, string name, Type[] types, ParameterModifier[] modifiers)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            if (types == null)
            {
                throw new ArgumentNullException("types");
            }

            if (types.Any(t => t == null))
            {
                throw new ArgumentNullException("types");
            }

            return GetMethod(type, name, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance, null, CallingConventions.Any, types, modifiers);
        }

        public static MethodInfo GetMethodWp7(this Type type, string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            if (types == null)
            {
                throw new ArgumentNullException("types");
            }

            if (types.Any(t => t == null))
            {
                throw new ArgumentNullException("types");
            }

            return GetMethod(type, name, bindingAttr, binder, CallingConventions.Any, types, modifiers);
        }

        public static MethodInfo GetMethodWp7(this Type type, string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            if (types == null)
            {
                throw new ArgumentNullException("types");
            }

            if (types.Any(t => t == null))
            {
                throw new ArgumentNullException("types");
            }

            return GetMethod(type, name, bindingAttr, binder, callConvention, types, modifiers);
        }

        public static MethodInfo[] GetMethodsWp7(this Type type)
        {
            return type.GetMethodsWp7(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
        }

        public static MethodInfo[] GetMethodsWp7(this Type type, BindingFlags flags)
        {
            if ((flags & BindingFlags.DeclaredOnly) == BindingFlags.DeclaredOnly)
            {
                return type.GetMethods(flags);
            }

            flags = flags | BindingFlags.DeclaredOnly;
            Type currentType = type;
            bool isBaseType = false;
            var methods = new List<MethodInfo>();

            while (currentType != null)
            {
                var newMethods = currentType.GetMethods(flags).Where(m => ShouldBeReturned(m, methods, isBaseType));
                methods.AddRange(newMethods);

                currentType = currentType.BaseType;
                if (!isBaseType)
                {
                    isBaseType = true;
                    flags = flags & (~BindingFlags.Static);
                }
            }

            return methods.ToArray();
        }

        private static MethodInfo GetMethod(
            Type type,
            string name,
            BindingFlags bindingFlags,
            Binder binder,
            CallingConventions callConvention,
            Type[] types,
            ParameterModifier[] modifiers)
        {
            if ((bindingFlags & BindingFlags.DeclaredOnly) == BindingFlags.DeclaredOnly)
            {
                return types == null
                       ? type.GetMethod(name, bindingFlags)
                       : type.GetMethod(name, bindingFlags, binder, callConvention, types, modifiers);
            }

            bool isBaseType = false;
            bindingFlags = bindingFlags | BindingFlags.DeclaredOnly;
            MethodInfo result = null;
            while (result == null && type != null)
            {
                result =
                    types == null
                       ? type.GetMethod(name, bindingFlags)
                       : type.GetMethod(name, bindingFlags, binder, callConvention, types, modifiers);
                if (isBaseType && result != null && result.IsPrivate)
                {
                    result = null;
                }

                type = type.BaseType;
                if (!isBaseType)
                {
                    isBaseType = true;
                    bindingFlags = bindingFlags & (~BindingFlags.Static);
                }
            }

            return result;
        }

        private static bool ShouldBeReturned(
            MethodInfo method,
            IEnumerable<MethodInfo> foundMethods,
            bool isCurrentTypeBaseType)
        {
            return !isCurrentTypeBaseType || (!method.IsPrivate && !HasAlreadyBeenFound(method, foundMethods));
        }

        private static bool HasAlreadyBeenFound(
            MethodInfo method,
            IEnumerable<MethodInfo> processedMethods)
        {
            if (!method.IsGenericMethodDefinition)
            {
                return processedMethods.Any(m => m.GetBaseDefinition().Equals(method.GetBaseDefinition()));
            }

            return processedMethods.Any(
                m => m.Name == method.Name &&
                     HaveSameGenericArguments(m, method) &&
                     HaveSameParameters(m, method));
        }

        private static bool HaveSameParameters(MethodInfo method1, MethodInfo method2)
        {
            var parameters1 = method1.GetParameters();
            var parameters2 = method2.GetParameters();
            return parameters1.Length == parameters2.Length &&
                   parameters1.All(parameters2.Contains);
        }

        private static bool HaveSameGenericArguments(MethodInfo method1, MethodInfo method2)
        {
            var genericArguments1 = method1.GetGenericArguments();
            var genericArguments2 = method2.GetGenericArguments();
            return genericArguments1.Length == genericArguments2.Length;
        }
    }

About the author

Remo Gloor

My name is Remo Gloor and I’m working as a software architect at bbv Software Services AG in Zug, Switzerland. My first expiriences with computers were a a little boy with basic programming on a C64, later Pascal an C on an Intel 8080. After finishing my study in software engineering I started working with C#.

Currently, I'm one of two Main Developers of the well known Ninject IoC containers. My other interests beside Ninject are TDD, Scrum and software architecture in general.

Add comment

By Remo Gloor

Recent Posts