Pytanie Metoda GetProperties () zwraca wszystkie właściwości hierarchii dziedziczenia interfejsu


Przy założeniu następującej hipotetycznej hierarchii dziedziczenia:

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

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

Korzystając z refleksji i wykonując następujące połączenie:

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

przyniesie tylko właściwości interfejsu IB, który jest "Name".

Jeśli mielibyśmy zrobić podobny test na poniższym kodzie,

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

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

telefon typeof(B).GetProperties(BindingFlags.Public | BindingFlags.Instance) zwróci tablicę PropertyInfo obiekty dla "ID" i "Name".

Czy istnieje prosty sposób na znalezienie wszystkich właściwości w hierarchii dziedziczenia dla interfejsów, jak w pierwszym przykładzie?


76
2017-12-11 09:51


pochodzenie




Odpowiedzi:


Udoskonaliłem przykładowy kod @Marc Gravel w użytecznej metodzie rozszerzenia, która obejmuje zarówno klasy, jak i interfejsy. Dodaje również najpierw właściwości interfejsu, który moim zdaniem jest oczekiwanym zachowaniem.

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

102
2018-03-14 22:36



Pure Brilliance! Dzięki temu rozwiązałeś problem, który miałem podobny do pytania op. - kamui
Na świecie nie ma wystarczającej liczby upvotes. - Chao
Twoje odniesienia do BindingFlags.FlattenHierarchy są zbędne, ponieważ używasz BindingFlags.Instance. - Chris Ward
Nie ma potrzeby rekurencji lub kolejek, ponieważ GetInterfaces () zwraca już wszystkie interfejsy zaimplementowane przez typ. Jak zauważył Marc, nie ma hierarchii, więc dlaczego mielibyśmy "rekursować się" na czymkolwiek? - glopes
@FrankyHollywood dlatego nie używasz GetProperties. Używasz GetInterfaces na twoim typie początkowym, który zwróci spłaszczoną listę wszystkich interfejsów i po prostu zrobi GetProperties na każdym interfejsie. Bez potrzeby rekursji. W interfejsach nie ma dziedziczenia ani typów bazowych. - glopes


Type.GetInterfaces zwraca spłaszczoną hierarchię, więc nie ma potrzeby rekurencyjnego zejścia.

Całą metodę można napisać znacznie bardziej zwięźle za pomocą 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());
}

49
2017-11-05 20:13



To zdecydowanie powinna być właściwa odpowiedź! Nie ma potrzeby powtarzania rekurencji. - glopes
Solidna odpowiedź dziękuję. Jak możemy uzyskać wartość nieruchomości w interfejsie podstawowym? - ilker unal
@ilkerunal: zwykły sposób: zadzwoń GetValue na pobranym PropertyInfo, przekazując twoją instancję (której wartość właściwości otrzymasz) jako parametr. Przykład: var list = new[] { 'a', 'b', 'c' }; var count = typeof(IList).GetPublicProperties().First(i => i.Name == "Count").GetValue(list); ← zwróci 3, mimo że Countjest zdefiniowany w ICollection, nie IList. - Douglas
To rozwiązanie ma wady, które mogą wielokrotnie zwracać właściwości o tej samej nazwie. Dalsze oczyszczanie wyników jest potrzebne dla odrębnej listy właściwości. Przyjęta odpowiedź jest bardziej poprawnym rozwiązaniem, ponieważ gwarantuje zwrócenie właściwości o unikalnych nazwach i czyni to, chwytając jeden z najbliższych w łańcuchu dziedziczenia. - user3524983
@ user3524983: Czy mógłbyś podać przykład? Po prostu wypróbowałem i otrzymałem tylko jedną właściwość dla danego imienia. - Douglas


Hierarchie interfejsu to ból - tak naprawdę nie "dziedziczą" jako takie, ponieważ możesz mieć wielu "rodziców" (z braku lepszego określenia).

"Spłaszczanie" (znowu, nie do końca trafne określenie) hierarchia może wymagać sprawdzenia wszystkich interfejsów, które interfejs implementuje i pracuje stamtąd ...

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

15
2017-12-11 10:02



Nie zgadzam się. Z całym szacunkiem dla Marca, ta odpowiedź również nie zdaje sobie sprawy, że GetInterfaces () zwraca już wszystkie implementowane interfejsy dla typu. Właśnie dlatego, że nie ma "hierarchii", nie ma potrzeby rekurencji lub kolejek. - glopes


Dokładnie ten sam problem został opisany tutaj.

FlattenHierarchy nie działa btw. (tylko na statycznych zmiennych. tak mówi w intellisense)

Obejście. Uważaj na duplikaty.

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

3
2017-12-11 10:06





to działało ładnie i zwięźle dla mnie w niestandardowym segregatorze modelu MVC. Powinien jednak móc ekstrapolować do dowolnego scenariusza refleksji. Jeszcze trochę śmierdzi, że to za mało

    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)

1
2017-12-14 14:38





Odpowiadając na @douglas i @ user3524983, poniższe odpowiedzi powinny odpowiedzieć na pytanie 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();
    }

lub dla pojedynczej nieruchomości:

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

OK następnym razem będę debugować go przed wysłaniem zamiast po :-)


0
2017-11-14 04:13