Разрешение имени члена во время выполнения

Учитывая тип, имя и подпись, как я могу выполнить поиск члена с именем name и подпись signature с использованием правил C # 7.4 (7.4 - это номер главы из спецификации языка C #) (или, по крайней мере, их часть ... Допустим, я могу жить с точным совпадением, без преобразований / приведений) во время выполнения? Мне нужно получить _1 _ / _ 2 _ / ... потому что тогда я должен использовать его с отражением (точнее, я пытаюсь создать конструктор Expression.ForEach (фабрика, способная создать дерево выражений, представляющее оператор foreach) , и чтобы быть идеальным по пикселям с C # foreach, я должен уметь набирать текст и искать метод GetEnumerator (в коллекции), свойство Current и метод MoveNext (в перечислителе), как написано в 8.8. .4)

Проблема (пример проблемы)

class C1
{
    public int Current { get; set; }

    public object MoveNext()
    {
        return null;
    }
}

class C2 : C1
{
    public new long Current { get; set; }

    public new bool MoveNext()
    {
        return true;
    }
}

class C3 : C2
{
}

var m = typeof(C3).GetMethods(); // I get both versions of MoveNext()
var p = typeof(C3).GetProperties(); // I get both versions of Current

Очевидно, что если я попробую typeof(C3).GetProperty("Current"), я получу AmbiguousMatchException исключение.

Аналогичная, но другая проблема существует с интерфейсами:

interface I0
{
    int Current { get; set; }
}

interface I1 : I0
{
    new long Current { get; set; }
}

interface I2 : I1, I0
{
    new object Current { get; set; }
}

interface I3 : I2
{
}

Здесь, если я попытаюсь выполнить typeof(I3).GetProperties(), я не получу свойство Current (и это что-то известное, см., Например, GetProperties (), чтобы вернуть все свойства для иерархии наследования интерфейсов), но я не могу просто сгладить интерфейсы, потому что тогда я не буду знать, кто скрывает кто.

Я знаю, что, вероятно, эта проблема решена где-то в пространстве имен Microsoft.CSharp.RuntimeBinder (объявленном в сборке Microsoft.CSharp). Это потому, что я пытаюсь использовать правила C #, и поиск членов необходим, когда у вас есть вызов динамического метода, но я не смог ничего найти (и тогда я бы получил Expression или, возможно, прямой вызов).

Поразмыслив, становится ясно, что в сборке Microsoft.VisualBasic есть что-то подобное. VB.NET поддерживает позднее связывание. Он находится в Microsoft.VisualBasic.CompilerServices.NewLateBinding, но не раскрывает методы с поздним ограничением.


person xanatos    schedule 10.07.2012    source источник
comment
Я рассмотрел возможность использования Binder для этого, как вы предлагали, но не понял, как это сделать. Вы можете выполнить привязку к правильному свойству для типа среды выполнения, но не к какому-либо другому типу, который он наследует (например, I3).   -  person svick    schedule 10.07.2012


Ответы (2)


(примечание: shadowing = hiding = new в определении метода / свойства / события в C #)

Никто не ответил, поэтому я пока выложу код, который приготовил. Я ненавижу публиковать 400 строк кода, но я хотел быть полным. Есть два основных метода: GetVisibleMethods и GetVisibleProperties. Это методы расширения класса Type. Они вернут общедоступные видимые (не затененные / непереопределенные) методы / свойства типа. Они должны даже обрабатывать сборки VB.NET (VB.NET обычно использует hide-by-name затенение вместо hidebysig, как это делает C #). Они кэшируют свой результат в двух статических коллекциях (Methods и Properties). Код предназначен для C # 4.0, поэтому я использую ConcurrentDictionary<T, U>. Если вы используете C # 3.5, вы можете заменить его на Dictionary<T, U>, но вы должны защитить его с помощью lock () {} при чтении из него и при записи в него.

Как это работает? Это работает рекурсией (я знаю, что рекурсия обычно плохая, но я надеюсь, что никто не создаст цепочку наследования уровня 1000).

Для «реальных» типов (неинтерфейсов) он поднимается на один уровень вверх (с использованием рекурсии, поэтому этот уровень может подняться на один уровень вверх и т. Д.) И из возвращенного списка методов / свойств удаляет методы / свойства. это перегрузка / прячется.

Для интерфейсов все немного сложнее. Type.GetInterfaces() возвращает все унаследованные интерфейсы, игнорируя, унаследованы ли они напрямую или косвенно. Для каждого из этих интерфейсов рассчитывается список объявленных методов / свойств (посредством рекурсии). Этот список сочетается со списком методов / свойств, которые скрыты интерфейсом (_12 _ / _ 13_). Эти методы / свойства, скрытые тем или иным интерфейсом, удаляются из всех других методов / свойств, возвращаемых из интерфейсов (так что, если у вас есть I1 с Method1(int), I2 наследуется от I1, который повторно объявляет Method1(int) и при этом скрывает I1.Method1 и I3, унаследованные от I1 и I2, тот факт, что I2 скрывает I1.Method1, будет применен к методам, возвращаемым из исследования I1, поэтому удаление I1.Method1(int) (это происходит потому, что я не генерирую карту наследования для интерфейсов, я просто смотрю, что что скрывает)).

Из возвращенных коллекций методов / свойств можно использовать Linq для поиска искомого метода / свойства. Обратите внимание, что с интерфейсами вы можете найти более одного метода / свойства с данной подписью. Пример:

interface I1
{
    void Method1();
}

interface I2
{
    void Method1();
}

interface I3 : I1, I2
{
}

I3 вернет два Method1().

Код:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

public static class TypeEx
{
    /// <summary>
    /// Type, Tuple&lt;Methods of type, (for interfaces)methods of base interfaces shadowed&gt;
    /// </summary>
    public static readonly ConcurrentDictionary<Type, Tuple<MethodInfo[], HashSet<MethodInfo>>> Methods = new ConcurrentDictionary<Type, Tuple<MethodInfo[], HashSet<MethodInfo>>>();

    /// <summary>
    /// Type, Tuple&lt;Properties of type, (for interfaces)properties of base interfaces shadowed&gt;
    /// </summary>
    public static readonly ConcurrentDictionary<Type, Tuple<PropertyInfo[], HashSet<PropertyInfo>>> Properties = new ConcurrentDictionary<Type, Tuple<PropertyInfo[], HashSet<PropertyInfo>>>();

    public static MethodInfo[] GetVisibleMethods(this Type type)
    {
        if (type.IsInterface)
        {
            return (MethodInfo[])type.GetVisibleMethodsInterfaceImpl().Item1.Clone();
        }

        return (MethodInfo[])type.GetVisibleMethodsImpl().Clone();
    }

    public static PropertyInfo[] GetVisibleProperties(this Type type)
    {
        if (type.IsInterface)
        {
            return (PropertyInfo[])type.GetVisiblePropertiesInterfaceImpl().Item1.Clone();
        }

        return (PropertyInfo[])type.GetVisiblePropertiesImpl().Clone();
    }

    private static MethodInfo[] GetVisibleMethodsImpl(this Type type)
    {
        Tuple<MethodInfo[], HashSet<MethodInfo>> tuple;

        if (Methods.TryGetValue(type, out tuple))
        {
            return tuple.Item1;
        }

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

        if (type.BaseType == null)
        {
            Methods.TryAdd(type, Tuple.Create(methods, (HashSet<MethodInfo>)null));
            return methods;
        }

        var baseMethods = type.BaseType.GetVisibleMethodsImpl().ToList();

        foreach (var method in methods)
        {
            if (method.IsHideByName())
            {
                baseMethods.RemoveAll(p => p.Name == method.Name);
            }
            else
            {
                int numGenericArguments = method.GetGenericArguments().Length;
                var parameters = method.GetParameters();

                baseMethods.RemoveAll(p =>
                {
                    if (!method.EqualSignature(numGenericArguments, parameters, p))
                    {
                        return false;
                    }

                    return true;
                });
            }
        }

        if (baseMethods.Count == 0)
        {
            Methods.TryAdd(type, Tuple.Create(methods, (HashSet<MethodInfo>)null));
            return methods;
        }

        var methods3 = new MethodInfo[methods.Length + baseMethods.Count];

        Array.Copy(methods, 0, methods3, 0, methods.Length);
        baseMethods.CopyTo(methods3, methods.Length);

        Methods.TryAdd(type, Tuple.Create(methods3, (HashSet<MethodInfo>)null));
        return methods3;
    }

    private static Tuple<MethodInfo[], HashSet<MethodInfo>> GetVisibleMethodsInterfaceImpl(this Type type)
    {
        Tuple<MethodInfo[], HashSet<MethodInfo>> tuple;

        if (Methods.TryGetValue(type, out tuple))
        {
            return tuple;
        }

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

        var baseInterfaces = type.GetInterfaces();

        if (baseInterfaces.Length == 0)
        {
            tuple = Tuple.Create(methods, new HashSet<MethodInfo>());
            Methods.TryAdd(type, tuple);
            return tuple;
        }

        var baseMethods = new List<MethodInfo>();

        var baseMethodsTemp = new MethodInfo[baseInterfaces.Length][];

        var shadowedMethods = new HashSet<MethodInfo>();

        for (int i = 0; i < baseInterfaces.Length; i++)
        {
            var tuple2 = baseInterfaces[i].GetVisibleMethodsInterfaceImpl();
            baseMethodsTemp[i] = tuple2.Item1;
            shadowedMethods.UnionWith(tuple2.Item2);
        }

        for (int i = 0; i < baseInterfaces.Length; i++)
        {
            baseMethods.AddRange(baseMethodsTemp[i].Where(p => !shadowedMethods.Contains(p)));
        }

        foreach (var method in methods)
        {
            if (method.IsHideByName())
            {
                baseMethods.RemoveAll(p =>
                {
                    if (p.Name == method.Name)
                    {
                        shadowedMethods.Add(p);
                        return true;
                    }

                    return false;
                });
            }
            else
            {
                int numGenericArguments = method.GetGenericArguments().Length;
                var parameters = method.GetParameters();

                baseMethods.RemoveAll(p =>
                {
                    if (!method.EqualSignature(numGenericArguments, parameters, p))
                    {
                        return false;
                    }

                    shadowedMethods.Add(p);
                    return true;
                });
            }
        }

        if (baseMethods.Count == 0)
        {
            tuple = Tuple.Create(methods, shadowedMethods);
            Methods.TryAdd(type, tuple);
            return tuple;
        }

        var methods3 = new MethodInfo[methods.Length + baseMethods.Count];

        Array.Copy(methods, 0, methods3, 0, methods.Length);
        baseMethods.CopyTo(methods3, methods.Length);

        tuple = Tuple.Create(methods3, shadowedMethods);
        Methods.TryAdd(type, tuple);
        return tuple;
    }

    private static PropertyInfo[] GetVisiblePropertiesImpl(this Type type)
    {
        Tuple<PropertyInfo[], HashSet<PropertyInfo>> tuple;

        if (Properties.TryGetValue(type, out tuple))
        {
            return tuple.Item1;
        }

        var properties = type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public);

        if (type.BaseType == null)
        {
            Properties.TryAdd(type, Tuple.Create(properties, (HashSet<PropertyInfo>)null));
            return properties;
        }

        var baseProperties = type.BaseType.GetVisiblePropertiesImpl().ToList();

        foreach (var property in properties)
        {
            if (property.IsHideByName())
            {
                baseProperties.RemoveAll(p => p.Name == property.Name);
            }
            else
            {
                var indexers = property.GetIndexParameters();

                baseProperties.RemoveAll(p =>
                {
                    if (!property.EqualSignature(indexers, p))
                    {
                        return false;
                    }

                    return true;
                });
            }
        }

        if (baseProperties.Count == 0)
        {
            Properties.TryAdd(type, Tuple.Create(properties, (HashSet<PropertyInfo>)null));
            return properties;
        }

        var properties3 = new PropertyInfo[properties.Length + baseProperties.Count];

        Array.Copy(properties, 0, properties3, 0, properties.Length);
        baseProperties.CopyTo(properties3, properties.Length);

        Properties.TryAdd(type, Tuple.Create(properties3, (HashSet<PropertyInfo>)null));
        return properties3;
    }

    private static Tuple<PropertyInfo[], HashSet<PropertyInfo>> GetVisiblePropertiesInterfaceImpl(this Type type)
    {
        Tuple<PropertyInfo[], HashSet<PropertyInfo>> tuple;

        if (Properties.TryGetValue(type, out tuple))
        {
            return tuple;
        }

        var properties = type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);

        var baseInterfaces = type.GetInterfaces();

        if (baseInterfaces.Length == 0)
        {
            tuple = Tuple.Create(properties, new HashSet<PropertyInfo>());
            Properties.TryAdd(type, tuple);
            return tuple;
        }

        var baseProperties = new List<PropertyInfo>();

        var basePropertiesTemp = new PropertyInfo[baseInterfaces.Length][];

        var shadowedProperties = new HashSet<PropertyInfo>();

        for (int i = 0; i < baseInterfaces.Length; i++)
        {
            var tuple2 = baseInterfaces[i].GetVisiblePropertiesInterfaceImpl();
            basePropertiesTemp[i] = tuple2.Item1;
            shadowedProperties.UnionWith(tuple2.Item2);
        }

        for (int i = 0; i < baseInterfaces.Length; i++)
        {
            baseProperties.AddRange(basePropertiesTemp[i].Where(p => !shadowedProperties.Contains(p)));
        }

        foreach (var property in properties)
        {
            if (property.IsHideByName())
            {
                baseProperties.RemoveAll(p =>
                {
                    if (p.Name == property.Name)
                    {
                        shadowedProperties.Add(p);
                        return true;
                    }

                    return false;
                });
            }
            else
            {
                var indexers = property.GetIndexParameters();

                baseProperties.RemoveAll(p =>
                {
                    if (!property.EqualSignature(indexers, p))
                    {
                        return false;
                    }

                    shadowedProperties.Add(p);
                    return true;
                });
            }
        }

        if (baseProperties.Count == 0)
        {
            tuple = Tuple.Create(properties, shadowedProperties);
            Properties.TryAdd(type, tuple);
            return tuple;
        }

        var properties3 = new PropertyInfo[properties.Length + baseProperties.Count];

        Array.Copy(properties, 0, properties3, 0, properties.Length);
        baseProperties.CopyTo(properties3, properties.Length);

        tuple = Tuple.Create(properties3, shadowedProperties);
        Properties.TryAdd(type, tuple);
        return tuple;
    }

    private static bool EqualSignature(this MethodInfo method1, int numGenericArguments1, ParameterInfo[] parameters1, MethodInfo method2)
    {
        // To shadow by signature a method must have same name, same number of 
        // generic arguments, same number of parameters and same parameters' type
        if (method1.Name != method2.Name)
        {
            return false;
        }

        if (numGenericArguments1 != method2.GetGenericArguments().Length)
        {
            return false;
        }

        var parameters2 = method2.GetParameters();

        if (!parameters1.EqualParameterTypes(parameters2))
        {
            return false;
        }

        return true;
    }

    private static bool EqualSignature(this PropertyInfo property1, ParameterInfo[] indexers1, PropertyInfo property2)
    {
        // To shadow by signature a property must have same name, 
        // same number of indexers and same indexers' type
        if (property1.Name != property2.Name)
        {
            return false;
        }

        var parameters2 = property1.GetIndexParameters();

        if (!indexers1.EqualParameterTypes(parameters2))
        {
            return false;
        }

        return true;
    }

    private static bool EqualParameterTypes(this ParameterInfo[] parameters1, ParameterInfo[] parameters2)
    {
        if (parameters1.Length != parameters2.Length)
        {
            return false;
        }

        for (int i = 0; i < parameters1.Length; i++)
        {
            if (parameters1[i].IsOut != parameters2[i].IsOut)
            {
                return false;
            }

            if (parameters1[i].ParameterType.IsGenericParameter)
            {
                if (!parameters2[i].ParameterType.IsGenericParameter)
                {
                    return false;
                }

                if (parameters1[i].ParameterType.GenericParameterPosition != parameters2[i].ParameterType.GenericParameterPosition)
                {
                    return false;
                }
            }
            else if (parameters1[i].ParameterType != parameters2[i].ParameterType)
            {
                return false;
            }
        }

        return true;
    }

    private static bool IsHideByName(this MethodInfo method)
    {
        if (!method.Attributes.HasFlag(MethodAttributes.HideBySig) && (!method.Attributes.HasFlag(MethodAttributes.Virtual) || method.Attributes.HasFlag(MethodAttributes.NewSlot)))
        {
            return true;
        }

        return false;
    }

    private static bool IsHideByName(this PropertyInfo property)
    {
        var get = property.GetGetMethod();

        if (get != null && get.IsHideByName())
        {
            return true;
        }

        var set = property.GetSetMethod();

        if (set != null && set.IsHideByName())
        {
            return true;
        }

        return false;
    }
}
person xanatos    schedule 12.07.2012
comment
Рекурсия - это не «обычно плохо». Да, он может взорвать ваш стек, но это не значит, что вы не должны его использовать, просто в некоторых случаях нужно быть осторожным. - person svick; 14.07.2012
comment
Уф, это сэкономило мне КУЧУ времени. Спасибо! - person Dave Lowther; 31.08.2016

Используйте перегруженный метод с параметром BindingFlags.

GetProperties(BindingFlags.DeclaredOnly)
person jorel    schedule 10.07.2012
comment
Бип! Неверный ответ :-) Это причина того, что там пустой C3. Current является частью C2. - person xanatos; 10.07.2012