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?

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

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

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

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

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


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)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.