Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WP7: Type.GetMethods throws MethodAccessException. Is there a workaround for this bug?

I try to get a list of all methods of a type. Type provides the GetMethods method to do this. But unfortunately it seems to be incorrectly implemented. It works properly as long as there is no overridden generic method on the reflected type. In this special case a MethodAccessException is thrown.

Does anyone have a workaround for this WP7 bug? I'm fine if all methods except the generic ones are returned.

Here is a sample of a class that will throw an exception. Note: the none generic return value is intended to prove that the return value is not involved in the problem. Furthermore, the base method can be changed to abstract and the problem still remains.

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>();
    }
}
like image 243
Remo Gloor Avatar asked Apr 12 '11 17:04

Remo Gloor


3 Answers

I'd grab them all (note the BindingFlags.DeclaredOnly in GetMethods) and then filter out those that are generic methods (MethodInfo.IsGenericMethod).

Sorry for the VB, I know the whole world wants C# these days, but...

Public Function GetListOfMethods() As List(Of MethodInfo)
    Dim d As New DerivedClassWithGenericMethod
    Dim myArrayMethodInfo() As Reflection.MethodInfo
    myArrayMethodInfo = d.GetType.GetMethods(BindingFlags.Instance _
                                              Or BindingFlags.Public _
                                              Or BindingFlags.DeclaredOnly)

    Dim myArrayMethodInfoList As New List(Of MethodInfo)
    For Each m As MethodInfo In myArrayMethodInfo
        If Not m.IsGenericMethod Then
            myArrayMethodInfoList.Add(m)
        End If
    Next
    Return myArrayMethodInfoList
End Function

I just tested on WP7 using your sample classes and it works fine.

like image 111
Todd Main Avatar answered Oct 17 '22 15:10

Todd Main


Finally I got it working. The following Type extension methods return exactly the same result as the .NET 4.0 implementation but without the MethodAccess exceptions thrown by WP7:

public static class TypeExtensions
{
    public static MethodInfo GetMethodWp7Workaround(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 GetMethodWp7Workaround(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 GetMethodWp7Workaround(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 GetMethodWp7Workaround(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 GetMethodWp7Workaround(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 GetMethodWp7Workaround(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);
    }

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

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

    public static MethodInfo[] GetMethodsWp7Workaround(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 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;
    }
}

You will get the same result for the following class with .NET 4.0 and this workaround on WP7:

internal class TestBaseClass
{
    private static void BasePrivateStaticMethod() { }

    private void BasePrivateMethod() { }

    private void BasePrivateGenericMethod<T>() { }

    protected static void BaseProtectedStaticMethod() { }

    protected void BaseProtectedMethod() { }

    protected void BaseProtectedGenericMethod<T>() { }

    protected virtual void OverriddenProtectedMethod() { }

    protected virtual void OverriddenProtectedGenericMethod<T>() { }

    internal static void BaseInternalStaticMethod() { }

    internal void BaseInternalMethod() { }

    internal void BaseInternalGenericMethod<T>() { }

    internal virtual void OverriddenInternalMethod() { }

    internal virtual void OverriddenInternalGenericMethod<T>() { }

    public static void BasePublicStaticMethod() { }

    public void BasePublicMethod() { }

    public void BasePublicGenericMethod<T>() { }

    public virtual void OverriddenPublicMethod() { }

    public virtual void OverriddenPublicGenericMethod<T>() { }
}

internal class TestClass : TestBaseClass
{
    public string Property
    {
        get { return null; }
    }

    private static void PrivateStaticMethod() { }

    private void PrivateMethod() { }

    private void PrivateGenericMethod<T>() { }

    protected static void ProtectedStaticMethod() { }

    protected void ProtectedMethod() { }

    protected static void ProtectedGenericMethod<T>() { }

    internal static void InternalGenericMethod<T>() { }

    internal void InternalMethod() { }

    internal static void InternalStaticMethod() { }

    public static void PublicStaticMethod() { }

    public void PublicMethod() { }

    public static void PublicGenericMethod<T>() { }

    internal override void OverriddenInternalMethod()
    {
        base.OverriddenInternalMethod();
    }

    protected override void OverriddenProtectedMethod()
    {
        base.OverriddenProtectedMethod();
    }

    public override void OverriddenPublicMethod()
    {
        base.OverriddenPublicMethod();
    }

    internal override void OverriddenInternalGenericMethod<T>()
    {
        base.OverriddenInternalGenericMethod<T>();
    }

    protected override void OverriddenProtectedGenericMethod<T>()
    {
        base.OverriddenProtectedGenericMethod<T>();
    }

    public override void OverriddenPublicGenericMethod<T>()
    {
        base.OverriddenPublicGenericMethod<T>();
    }
}
like image 20
Remo Gloor Avatar answered Oct 17 '22 17:10

Remo Gloor


Could you actually be misinterpreting the behavior you are seeing? I am thinking you are experiencing a valid security access issue and nothing to do with the implementation of reflection on WP7 (other than its security model).

Look at this post. Could the type you are reflecting, any of the methods in question or the Type assigned to T in your particular case be marked as security critical with the SecurityCriticalAttribute?

like image 1
dkackman Avatar answered Oct 17 '22 16:10

dkackman