GetProperties (), aby zwrócić wszystkie właściwości dla hierarchii dziedziczenia interfejsu

98

Zakładając następującą hipotetyczną hierarchię 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 wezwanie:

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

zwróci tylko właściwości interfejsu IB, którym jest „ Name”.

Gdybyśmy mieli wykonać podobny test na poniższym kodzie,

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

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

wywołanie typeof(B).GetProperties(BindingFlags.Public | BindingFlags.Instance)zwróci tablicę PropertyInfoobiektów dla „ ID” i „ Name”.

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

sduplooy
źródło

Odpowiedzi:

111

Przekształciłem przykładowy kod @Marc Gravel w użyteczną metodę rozszerzającą obejmującą zarówno klasy, jak i interfejsy. Dodaje również najpierw właściwości interfejsu, które moim zdaniem są oczekiwane.

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);
}
mit
źródło
2
Czysty blask! Dziękuję, że rozwiązałem problem podobny do pytania operatora.
kamui
1
Twoje odwołania do BindingFlags.FlattenHierarchy są zbędne, ponieważ używasz również BindingFlags.Instance.
Chris Ward,
1
Zaimplementowałem to, ale z Stack<Type>rozszerzeniem zamiast Queue<>. Ze stosem przodkowie utrzymują kolejność taką, że interface IFoo : IBar, IBazgdzie IBar : IBubblei "IBaz: IFlubber , the order of reflection becomes: IBar , IBubble , IBaz , IFlubber , IFoo".
IAbstract
4
Nie ma potrzeby rekursji ani kolejek, ponieważ GetInterfaces () zwraca już wszystkie interfejsy zaimplementowane przez typ. Jak zauważył Marc, nie ma hierarchii, więc dlaczego mielibyśmy na czymkolwiek „powtarzać”?
glopes
3
@FrankyHollywood, dlatego nie używasz GetProperties. Używasz GetInterfacesna swoim typie początkowym, który zwróci spłaszczoną listę wszystkich interfejsów i po prostu zrobi to GetPropertiesna każdym interfejsie. Nie ma potrzeby rekursji. W interfejsach nie ma dziedziczenia ani typów podstawowych.
glopes
80

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

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());
}
Douglas
źródło
8
To zdecydowanie powinna być dobra odpowiedź! Nie ma potrzeby niezręcznej rekursji.
glopes
Solidna odpowiedź dziękuję. Jak możemy uzyskać wartość właściwości w interfejsie podstawowym?
ilker unal
1
@ilkerunal: Zwykły sposób: wywołaj GetValuepobrane PropertyInfo, przekazując jako parametr swoją instancję (której wartość właściwości ma zostać pobrana ). 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 zdefiniowane w środku ICollection, a nie IList.
Douglas
2
To rozwiązanie ma wady polegające na tym, że może wielokrotnie zwracać właściwości o tej samej nazwie. Konieczne jest dalsze czyszczenie wyników dla odrębnej listy właściwości. Zaakceptowana odpowiedź jest bardziej poprawnym rozwiązaniem, ponieważ gwarantuje zwrócenie właściwości o unikalnych nazwach i robi to poprzez przejęcie tej najbliższej w łańcuchu dziedziczenia.
user3524983
1
@AntWaters GetInterfaces nie jest wymagane, jeśli typejest klasą, ponieważ konkretna klasa MUSI implementować wszystkie właściwości zdefiniowane we wszystkich interfejsach w łańcuchu dziedziczenia. Użycie GetInterfacesw tym scenariuszu spowodowałoby zduplikowanie WSZYSTKICH właściwości.
Chris Schaller
15

Hierarchie interfejsów są uciążliwe - tak naprawdę nie „dziedziczą” jako takie, ponieważ można mieć wielu „rodziców” (z braku lepszego terminu).

„Spłaszczenie” (znowu, niezupełnie właściwy termin) hierarchia może wymagać sprawdzenia wszystkich interfejsów implementowanych przez interfejs i pracy z tego miejsca ...

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);
            }
        }
    }
}
Marc Gravell
źródło
7
Nie zgadzam się. Z całym szacunkiem dla Marca, ta odpowiedź również nie zdaje sobie sprawy, że GetInterfaces () już zwraca wszystkie zaimplementowane interfejsy dla typu. Właśnie dlatego, że nie ma „hierarchii”, nie ma potrzeby rekursji ani kolejek.
glopes
Zastanawiam się, czy użycie HashSet<Type>for consideredjest lepsze niż użycie List<Type>tutaj? Zawartość na liście ma pętlę, a ta pętla jest umieszczona w pętli foreach. Uważam, że zaszkodziłoby to wydajności, jeśli jest wystarczająco dużo elementów, a kod powinien być krytycznie szybki.
Beznadziejny
3

Dokładnie ten sam problem ma obejście opisane tutaj .

Przy okazji FlattenHierarchy nie działa. (tylko na zmiennych statycznych. tak mówi w intelisense)

Obejście problemu. 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);
Wilk 5
źródło
2

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 indywidualnej 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 zdebuguję to przed wysłaniem zamiast po :-)

sjb-sjb
źródło
1

to działało ładnie i zwięźle dla mnie w segregatorze niestandardowym modelu MVC. Powinien jednak móc dokonać ekstrapolacji na dowolny scenariusz refleksji. Wciąż śmierdzi, że to przeszł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)
Derek Strickland
źródło