WP7: Type.GetMethods выдает исключение MethodAccessException. Есть ли обходной путь для этой ошибки? - PullRequest
6 голосов
/ 12 апреля 2011

Я пытаюсь получить список всех методов типа. Тип предоставляет метод GetMethods для этого. Но, к сожалению, это, похоже, неправильно реализовано. Он работает правильно, пока в отраженном типе нет переопределенного универсального метода . В этом особом случае выдается исключение MethodAccessException.

У кого-нибудь есть обходной путь для этой ошибки WP7? Я в порядке, если возвращаются все методы, кроме общих.

Вот пример класса, который сгенерирует исключение. Примечание: возвращаемое значение без универсального значения предназначено для доказательства того, что возвращаемое значение не связано с проблемой. Кроме того, базовый метод может быть изменен на абстрактный, и проблема все еще остается.

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

Ответы [ 5 ]

3 голосов
/ 09 мая 2011

Наконец-то у меня все заработало.Следующие методы расширения типа возвращают точно такой же результат, что и реализация .NET 4.0, но без исключений MethodAccess, генерируемых 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;
    }
}

Вы получите тот же результат для следующего класса с .NET 4.0, и этоОбходной путь на 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>();
    }
}
3 голосов
/ 05 мая 2011

Я бы взял их все (обратите внимание на BindingFlags.DeclaredOnly в GetMethods), а затем отфильтровал те, которые являются общими методами (MethodInfo.IsGenericMethod).

Извините за VB, я знаю, что сегодня весь мир хочет C #, но ...

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

Я только что проверил на WP7, используя ваши примеры классов, и он отлично работает.

1 голос
/ 09 мая 2011

Я должен признать, что сомневался в том, что вы сообщаете, но это действительно легко воспроизводимо и выглядит для меня как ошибка.Я думаю, вы должны сообщить об этом на Microsoft connect .

Что касается обходных путей ...

Это даст вам члены производного класса, а затем базовый класс (включая универсальный) без ошибок:

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

Это можно обобщить (быстрая и грязная реализация) примерно так (EqualityComprarer должен отфильтровывать члены базового класса, которые переопределяются или скрываются производными классами):

 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);
1 голос
/ 09 мая 2011

Не могли бы вы на самом деле неправильно истолковать поведение, которое вы видите?Я думаю, что вы испытываете действительную проблему с безопасным доступом и не имеете ничего общего с реализацией рефлексии на WP7 (кроме ее модели безопасности).

Посмотрите на этот пост .Может ли тип, который вы отображаете, любой из рассматриваемых методов или тип, назначенный для T в вашем конкретном случае, помечаться как критический для безопасности с SecurityCriticalAttribute ?

0 голосов
/ 27 апреля 2011

Theres a GetMethods(BindingFlags). Попробуйте отфильтровать методы, которые вы получаете с аргументом BindingFlags.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...