GetProperties (), um alle Eigenschaften für eine Schnittstellenvererbungshierarchie zurückzugeben

96

Angenommen, die folgende hypothetische Vererbungshierarchie:

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

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

Reflektion verwenden und folgenden Anruf tätigen:

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

gibt nur die Eigenschaften der Schnittstelle aus IB, die " Name" ist.

Wenn wir einen ähnlichen Test mit dem folgenden Code durchführen würden,

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

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

Der Aufruf typeof(B).GetProperties(BindingFlags.Public | BindingFlags.Instance)gibt ein Array von PropertyInfoObjekten für " ID" und " Name" zurück.

Gibt es eine einfache Möglichkeit, alle Eigenschaften in der Vererbungshierarchie für Schnittstellen wie im ersten Beispiel zu finden?

sduplooy
quelle

Antworten:

112

Ich habe den Beispielcode von @Marc Gravel in eine nützliche Erweiterungsmethode umgewandelt, die sowohl Klassen als auch Schnittstellen kapselt. Außerdem werden zuerst die Schnittstelleneigenschaften hinzugefügt, von denen ich glaube, dass sie das erwartete Verhalten sind.

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);
}
Mythos
quelle
2
Brillanz pur! Vielen Dank, dass dies ein Problem gelöst hat, das ich ähnlich wie die Frage der Operation hatte.
Kamui
1
Ihre Verweise auf BindingFlags.FlattenHierarchy sind überflüssig, da Sie auch BindingFlags.Instance verwenden.
Chris Ward
1
Ich habe dies aber mit einem Stack<Type>anstelle von a implementiert Queue<>. Mit einem Stapel behält die Abstammung eine Reihenfolge bei, in der interface IFoo : IBar, IBazwo IBar : IBubbleund 'IBaz: IFlubber , the order of reflection becomes: IBar , IBubble , IBaz , IFlubber , IFoo'.
IAbstract
4
Rekursion oder Warteschlangen sind nicht erforderlich, da GetInterfaces () bereits alle von einem Typ implementierten Schnittstellen zurückgibt. Wie Marc bemerkte, gibt es keine Hierarchie. Warum sollten wir also auf irgendetwas "zurückgreifen" müssen?
Glopes
3
@FrankyHollywood deshalb benutzt du nicht GetProperties. Sie verwenden GetInterfacesfür Ihren Starttyp, der die abgeflachte Liste aller Schnittstellen zurückgibt und dies einfach GetPropertiesfür jede Schnittstelle tut . Keine Rekursion erforderlich. Es gibt keine Vererbung oder Basistypen in Schnittstellen.
Glopes
77

Type.GetInterfaces Gibt die abgeflachte Hierarchie zurück, sodass kein rekursiver Abstieg erforderlich ist.

Die gesamte Methode kann mit LINQ viel präziser geschrieben werden:

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
quelle
8
Dies sollte auf jeden Fall die richtige Antwort sein! Keine Notwendigkeit für die klobige Rekursion.
Glopes
Solide Antwort danke. Wie können wir den Wert einer Eigenschaft in der Basisschnittstelle erhalten?
Ilker Unal
1
@ilkerunal: Der übliche Weg: Rufen Sie GetValuedie abgerufene PropertyInfoInstanz auf und übergeben Sie Ihre Instanz (deren Eigenschaftswert abgerufen werden soll) als Parameter. Beispiel: var list = new[] { 'a', 'b', 'c' }; var count = typeof(IList).GetPublicProperties().First(i => i.Name == "Count").GetValue(list);← gibt 3 zurück, obwohl dies Countin definiert ist ICollection, nicht IList.
Douglas
2
Diese Lösung weist insofern Mängel auf, als sie möglicherweise gleichnamige Eigenschaften mehrmals zurückgibt. Eine weitere Bereinigung der Ergebnisse ist für eine eindeutige Eigenschaftsliste erforderlich. Die akzeptierte Antwort ist die korrektere Lösung, da sie die Rückgabe von Eigenschaften mit eindeutigen Namen garantiert, indem diejenige ausgewählt wird, die in der Vererbungskette am nächsten liegt.
user3524983
1
@AntWaters GetInterfaces ist nicht erforderlich, wenn typees sich um eine Klasse handelt, da die konkrete Klasse alle Eigenschaften implementieren MUSS , die in allen Schnittstellen entlang der Vererbungskette definiert sind. Die Verwendung in diesem Szenario würde dazu führen, dass ALLE Eigenschaften dupliziert werden. GetInterfaces
Chris Schaller
15

Schnittstellenhierarchien sind ein Schmerz - sie "erben" nicht wirklich als solche, da Sie mehrere "Eltern" haben können (aus Mangel an einem besseren Begriff).

"Abflachen" (wieder nicht ganz der richtige Begriff) Die Hierarchie könnte das Überprüfen aller von der Schnittstelle implementierten Schnittstellen und das Arbeiten von dort aus beinhalten ...

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
quelle
7
Ich bin nicht einverstanden. Bei allem Respekt vor Marc erkennt diese Antwort auch nicht, dass GetInterfaces () bereits alle implementierten Schnittstellen für einen Typ zurückgibt. Gerade weil es keine "Hierarchie" gibt, sind keine Rekursionen oder Warteschlangen erforderlich.
Glopes
3

Genau das gleiche Problem hat eine hier beschriebene Problemumgehung .

FlattenHierarchy funktioniert übrigens nicht. (nur auf statischen vars. sagt so in Intellisense)

Problemumgehung. Vorsicht vor Duplikaten.

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

Als Antwort auf @douglas und @ user3524983 sollte Folgendes die Frage des OP beantworten:

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

oder für eine einzelne Immobilie:

    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, das nächste Mal werde ich es vor dem Posten debuggen anstatt nach :-)

sjb-sjb
quelle
1

Dies funktionierte gut und knapp für mich in einem benutzerdefinierten MVC-Modellordner. Sollte jedoch in der Lage sein, auf jedes Reflexionsszenario zu extrapolieren. Immer noch stinkt es irgendwie, dass es zu pass ist

    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
quelle