GetProperties () для повернення всіх властивостей для ієрархії успадкування інтерфейсу


96

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

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".

Чи є простий спосіб знайти всі властивості в ієрархії успадкування для інтерфейсів, як у першому прикладі?

Відповіді:


112

Я переробив приклад @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);
}

2
Чистий блиск! Дякую, це вирішило проблему, яку я мав, подібну до запитання оператора.
kamui

1
Ваші посилання на BindingFlags.FlattenHierarchy є зайвими, оскільки ви також використовуєте BindingFlags.Instance.
Chris Ward

1
Я реалізував це, але Stack<Type>замість Queue<>. З стеком, родовід підтримує порядок такої , що , interface IFoo : IBar, IBazде IBar : IBubbleі «IBaz: IFlubber , the order of reflection becomes: IBar , IBubble , IBaz , IFlubber , IFoo`.
IAbstract

4
Немає необхідності в рекурсії або чергах, оскільки GetInterfaces () вже повертає всі інтерфейси, реалізовані типом. Як зазначав Марк, ієрархії не існує, то чому ми повинні "повторювати" що-небудь?
glopes

3
@FrankyHollywood, тому ти не використовуєш GetProperties. Ви використовуєте GetInterfacesна своєму типі запуску, який поверне сплощений список усіх інтерфейсів і просто зробить це GetPropertiesна кожному інтерфейсі. Немає необхідності в рекурсії. У інтерфейсах немає спадкових або базових типів.
glopes

77

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

8
Це неодмінно має бути правильною відповіддю! Немає необхідності в незграбній рекурсії.
glopes

Тверда відповідь спасибі. Як ми можемо отримати значення властивості в базовому інтерфейсі?
ilker unal

1
@ilkerunal: Звичайний спосіб: Виклик GetValueотриманого PropertyInfo, передаючи свій екземпляр (значення властивості якого отримати) як параметр. Приклад: var list = new[] { 'a', 'b', 'c' }; var count = typeof(IList).GetPublicProperties().First(i => i.Name == "Count").GetValue(list);← поверне 3, хоча Countвизначено всередині ICollection, а не IList.
Дуглас,

2
Це рішення має недоліки, оскільки воно може повертати властивості з однією назвою кілька разів. Подальше очищення результатів потрібно для окремого списку властивостей. Прийнята відповідь є більш правильним рішенням, оскільки вона гарантує повернення властивостей з унікальними іменами, і робить це, захоплюючи найближче в ланцюжку успадкування.
користувач3524983

1
@AntWaters GetInterfaces не потрібно, якщо typeце клас, оскільки конкретний клас ПОВИНЕН реалізувати всі властивості, визначені у всіх Інтерфейсах по ланцюжку успадкування. Використання GetInterfacesв цьому сценарії призведе до дублювання ВСІХ властивостей.
Кріс Шаллер,

15

Ієрархії інтерфейсу - це біль - вони насправді не «успадковують» як такі, оскільки у вас може бути кілька «батьків» (через відсутність кращого терміну).

"Згладжування" (знову ж таки, не зовсім правильний термін) ієрархія може передбачати перевірку всіх інтерфейсів, які реалізує інтерфейс, і роботи звідти ...

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

7
Я не погоджуюсь. При всій повазі до Марка, ця відповідь також не усвідомлює, що GetInterfaces () вже повертає всі реалізовані інтерфейси для типу. Саме тому, що немає "ієрархії", немає необхідності в рекурсії або чергах.
glopes

3

Точно та ж проблема має спосіб обходу, описаний тут .

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

2

Відповідаючи на @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();
    }

Гаразд, наступного разу я налагоджуватиму його перед публікацією, а не після :-)


1

це вдало та стисло працювало для мене в користувальницькій підшивці моделі 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.