GetProperties () для возврата всех свойств иерархии наследования интерфейса.

Предполагая следующую гипотетическую иерархию наследования:

public interface IA
{
  int ID { get; set; }
}

public interface IB : IA
{
  string Name { get; set; }
}

Используя отражение и сделав следующий вызов:

typeof(IB).GetProperties(BindingFlags.Public | BindingFlags.Instance) 

даст только свойства интерфейса IB, который равен "Name".

Если бы мы провели аналогичный тест для следующего кода,

public abstract class A
{
  public int ID { get; set; }
}

public class B : A
{
  public string Name { get; set; }
}

вызов typeof(B).GetProperties(BindingFlags.Public | BindingFlags.Instance) вернет массив из PropertyInfo объектов для «ID» и «Name».

Есть ли простой способ найти все свойства в иерархии наследования для интерфейсов, как в первом примере?


person sduplooy    schedule 11.12.2008    source источник


Ответы (6)


Я переделал пример кода @Marc Gravel в полезный метод расширения, инкапсулирующий как классы, так и интерфейсы. Он также сначала добавляет свойства интерфейса, которые, как я считаю, являются ожидаемым поведением.

public static PropertyInfo[] GetPublicProperties(this Type type)
{
    if (type.IsInterface)
    {
        var propertyInfos = new List<PropertyInfo>();

        var considered = new List<Type>();
        var queue = new Queue<Type>();
        considered.Add(type);
        queue.Enqueue(type);
        while (queue.Count > 0)
        {
            var subType = queue.Dequeue();
            foreach (var subInterface in subType.GetInterfaces())
            {
                if (considered.Contains(subInterface)) continue;

                considered.Add(subInterface);
                queue.Enqueue(subInterface);
            }

            var typeProperties = subType.GetProperties(
                BindingFlags.FlattenHierarchy 
                | BindingFlags.Public 
                | BindingFlags.Instance);

            var newPropertyInfos = typeProperties
                .Where(x => !propertyInfos.Contains(x));

            propertyInfos.InsertRange(0, newPropertyInfos);
        }

        return propertyInfos.ToArray();
    }

    return type.GetProperties(BindingFlags.FlattenHierarchy
        | BindingFlags.Public | BindingFlags.Instance);
}
person mythz    schedule 14.03.2010
comment
Чистый блеск! Спасибо, что решила проблему, похожую на вопрос оператора. - person kamui; 12.04.2012
comment
Ваши ссылки на BindingFlags.FlattenHierarchy являются избыточными, поскольку вы также используете BindingFlags.Instance. - person Chris Ward; 27.09.2012
comment
Я реализовал это, но с Stack<Type> вместо Queue<>. Со стеком предки поддерживают такой порядок, что interface IFoo : IBar, IBaz, где IBar : IBubble и 'IBaz: IFlubber, the order of reflection becomes: IBar, IBubble, IBaz, IFlubber, IFoo`. - person IAbstract; 05.10.2014
comment
Нет необходимости в рекурсии или очередях, поскольку GetInterfaces () уже возвращает все интерфейсы, реализованные типом. Как заметил Марк, иерархии нет, так почему мы должны возвращаться к чему-либо? - person glopes; 08.08.2015
comment
@glopes, потому что свойства в базовых типах интерфейсов не возвращаются GetProperties. Даже с флагом FlattenHierarchy. - person FrankyHollywood; 31.08.2016
comment
@FrankyHollywood, поэтому вы не используете GetProperties. Вы используете GetInterfaces в своем начальном типе, который возвращает упорядоченный список всех интерфейсов и просто выполняет GetProperties для каждого интерфейса. Нет необходимости в рекурсии. В интерфейсах нет наследования или базовых типов. - person glopes; 31.08.2016
comment
@glopes Не могли бы вы опубликовать свое решение в качестве ответа? - person Jeff; 11.02.2019
comment
@ChrisWard Нет, BindingFlags.FlattenHierarchy указывает статические элементы, а BindingFlags.Instance указывает элементы экземпляра. Эти два понятия исключают друг друга. - person Jeff; 11.02.2019

Type.GetInterfaces возвращает уплощенную иерархию, поэтому нет необходимости в рекурсивном спуске.

Весь метод может быть написан намного более кратко, используя LINQ:

public static IEnumerable<PropertyInfo> GetPublicProperties(this Type type)
{
    if (!type.IsInterface)
        return type.GetProperties();

    return (new Type[] { type })
           .Concat(type.GetInterfaces())
           .SelectMany(i => i.GetProperties());
}
person Douglas    schedule 05.11.2014
comment
Это определенно должен быть правильный ответ! Нет необходимости в неуклюжей рекурсии. - person glopes; 08.08.2015
comment
Твердый ответ, спасибо. Как мы можем получить значение свойства в базовом интерфейсе? - person ilker unal; 04.11.2015
comment
У этого решения есть недостатки в том, что оно может возвращать свойства с одним и тем же именем несколько раз. Дальнейшая очистка результатов необходима для отдельного списка свойств. Принятый ответ является более правильным решением, поскольку он гарантирует возврат свойств с уникальными именами и делает это путем захвата ближайшего в цепочке наследования. - person user3524983; 24.08.2017
comment
@ user3524983: Не могли бы вы привести пример? Я просто попробовал и получил только одно свойство для данного имени. - person Douglas; 26.08.2017
comment
@Douglas Я думаю, это то, что имеет в виду @ user3524983. Name: String печатается дважды. Однако то же самое происходит с принятым решением ... - person Jeff; 11.02.2019
comment
@Jeff: Спасибо за разъяснения. Однако это было бы ожидаемым поведением, поскольку это два разных свойства, которые имеют один и тот же Name (но разные _ 2_). Даже компилятор предупреждает, что Derived.String скрывает унаследованный член Base.String. Реализующий тип может предоставлять разные явные реализации для двух, поэтому было бы ошибкой возвращать только одну. public class Implementation : Derived { string Base.String => "B"; string Derived.String => "D"; } - person Douglas; 12.02.2019
comment
Кто-нибудь знает, что это вернет, если type - это class, который наследует интерфейсы (возможно, дальше по иерархии)? Есть ли необходимость вызывать GetInterfaces даже для класса? - person Ant Waters; 02.05.2019
comment
@AntWaters GetInterfaces не требуется, если type является классом, потому что конкретный класс ДОЛЖЕН реализовывать все свойства, которые определены во всех интерфейсах вниз по цепочке наследования. Использование GetInterfaces в этом сценарии приведет к дублированию ВСЕХ свойств. - person Chris Schaller; 18.03.2020

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

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

interface ILow { void Low();}
interface IFoo : ILow { void Foo();}
interface IBar { void Bar();}
interface ITest : IFoo, IBar { void Test();}

static class Program
{
    static void Main()
    {
        List<Type> considered = new List<Type>();
        Queue<Type> queue = new Queue<Type>();
        considered.Add(typeof(ITest));
        queue.Enqueue(typeof(ITest));
        while (queue.Count > 0)
        {
            Type type = queue.Dequeue();
            Console.WriteLine("Considering " + type.Name);
            foreach (Type tmp in type.GetInterfaces())
            {
                if (!considered.Contains(tmp))
                {
                    considered.Add(tmp);
                    queue.Enqueue(tmp);
                }
            }
            foreach (var member in type.GetMembers())
            {
                Console.WriteLine(member.Name);
            }
        }
    }
}
person Marc Gravell    schedule 11.12.2008
comment
Я не согласен. При всем уважении к Марку, в этом ответе также не учитывается, что GetInterfaces () уже возвращает все реализованные интерфейсы для типа. Именно потому, что нет иерархии, нет необходимости в рекурсии или очередях. - person glopes; 08.08.2015
comment
Интересно, лучше ли использовать HashSet<Type> вместо considered, чем использовать List<Type> здесь? Contains on a List имеет цикл, и этот цикл помещается в цикл foreach, я считаю, что это ухудшит производительность, если будет достаточно элементов, а код должен быть критически быстрым. - person Hopeless; 05.10.2020

Точно такая же проблема имеет обходной путь, описанный здесь.

FlattenHierarchy не работает, кстати. (только для статических переменных. говорит об этом в intellisense)

Обходной путь. Остерегайтесь дубликатов.

PropertyInfo[] pis = typeof(IB).GetProperties(BindingFlags.Public | BindingFlags.Instance);
Type[] tt = typeof(IB).GetInterfaces();
PropertyInfo[] pis2 = tt[0].GetProperties(BindingFlags.Public | BindingFlags.Instance);
person Wolf5    schedule 11.12.2008

Отвечая на @douglas и @ user3524983, следующий ответ должен ответить на вопрос OP:

    static public IEnumerable<PropertyInfo> GetPropertiesAndInterfaceProperties(this Type type, BindingFlags bindingAttr = BindingFlags.Public | BindingFlags.Instance)
    {
        if (!type.IsInterface) {
            return type.GetProperties( bindingAttr);
        }

        return type.GetInterfaces().Union(new Type[] { type }).SelectMany(i => i.GetProperties(bindingAttr)).Distinct();
    }

или для отдельного объекта:

    static public PropertyInfo GetPropertyOrInterfaceProperty(this Type type, string propertyName, BindingFlags bindingAttr = BindingFlags.Public|BindingFlags.Instance)
    {
        if (!type.IsInterface) {
            return type.GetProperty(propertyName, bindingAttr);
        }

        return type.GetInterfaces().Union(new Type[] { type }).Select(i => i.GetProperty( propertyName, bindingAttr)).Distinct().Where(propertyInfo => propertyInfo != null).Single();
    }

Хорошо, в следующий раз я отлажу его перед публикацией, а не после :-)

person sjb-sjb    schedule 14.11.2017

это сработало для меня красиво и лаконично в настраиваемом связывателе модели MVC. Тем не менее, должна быть возможность экстраполировать на любой сценарий отражения. По-прежнему воняет, что это слишком сложно

    var props =  bindingContext.ModelType.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance).ToList();

    bindingContext.ModelType.GetInterfaces()
                      .ToList()
                      .ForEach(i => props.AddRange(i.GetProperties()));

    foreach (var property in props)
person Derek Strickland    schedule 14.12.2012