开发者

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

开发者 https://www.devze.com 2023-02-25 14:55 出处:网络
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 overrid

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


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.


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


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?


I gotta admit that I doubted what you are reporting but it is indeed easily reproducible and does look like a bug to me. I think you should report it on Microsoft connect.

As for work arounds...

This will get you the members of the derived class and then the base class (including the generic) without error:

var m = new DerivedClassWithGenericMethod();
foreach (var method in m.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
    Debug.WriteLine(method.Name);

foreach (var method in m.GetType().BaseType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
    Debug.WriteLine(method.Name);

This can be generalized (quick and dirty implementation) somewhat like so (The EqualityComprarer should filter out base class members that are overriden or hidden by derived classes):

 class MethodComparer : IEqualityComparer<MethodInfo>
    {
        public bool Equals(MethodInfo x, MethodInfo y)
        {
            return GetHashCode(x) == GetHashCode(y);
        }

        public int GetHashCode(MethodInfo obj)
        {
            int hash = obj.Name.GetHashCode();
            foreach (var param in obj.GetParameters())
                hash ^= param.ParameterType.GetHashCode();

            if (obj.IsGenericMethodDefinition)
            {
                hash ^= obj.GetGenericArguments().Length.GetHashCode();
            }
            else if (obj.IsGenericMethod)
            {
                foreach (var t in obj.GetGenericArguments())
                    hash ^= t.GetHashCode();
            }

            return hash;
        }
    }

    static class Ext
    {
        public static MethodInfo[] MyGetMethods(this Type t)
        {
            if (t == null)
                return new MethodInfo[] { };

            var methods = t.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);

            var baseMethods = from m in t.BaseType.MyGetMethods()
                    where !methods.Contains(m, new MethodComparer())
                    select m;

            return methods.Concat(baseMethods).ToArray();
        }
    }

var m = new DerivedClassWithGenericMethod();
foreach (var method in m.GetType().MyGetMethods())
    Debug.WriteLine(method.Name);


Theres a GetMethods(BindingFlags). Try filtering out the methods you are getting with the BindingFlags argument.

0

精彩评论

暂无评论...
验证码 换一张
取 消