2008-12-11 3 views
75

die folgenden hypothetischen Vererbungshierarchie Angenommen:GetProperties() alle Eigenschaften für eine Schnittstellenvererbungshierarchie zurückzukehren

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

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

die die Reflexion und macht den folgenden Aufruf:

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

nachgeben nur die Eigenschaften von Schnittstelle IB, die "Name" ist.

Wenn wir einen ähnlichen Test auf den folgenden Code zu tun, waren

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

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

der Anruf typeof(B).GetProperties(BindingFlags.Public | BindingFlags.Instance) wird eine Reihe von PropertyInfo Objekte für „ID“ und „Name“ zurückzukehren.

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

Antwort

99

Ich habe @ Marc Gravel Beispiel Code in eine nützliche Erweiterungsmethode eingekapselt kapselt beide Klassen und Schnittstellen. Es fügt auch die Schnittstelleneigenschaften zuerst hinzu, 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); 
} 
+1

Pure Brillanz! Danke, das hat ein Problem gelöst, das ich der OP-Frage ähnlich hatte. – kamui

+1

Dafür gibt es nicht genug Upvotes auf der Welt. – Chao

+1

Ihre Verweise auf BindingFlags.FlattenHierarchy sind redundant, da Sie auch BindingFlags.Instance verwenden. –

15

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

„Verflachung“ (auch hier nicht ganz der richtige Begriff) die Hierarchie könnte die Überprüfung für alle Schnittstellen beinhalten, die die Schnittstelle implementiert und arbeitet von dort aus ...

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

stimme ich nicht zu. Bei allem Respekt für Marc wird in dieser Antwort auch nicht erkannt, dass GetInterfaces() bereits alle implementierten Schnittstellen für einen Typ zurückgibt. Gerade weil es keine "Hierarchie" gibt, ist keine Rekursion oder Warteschlangen nötig. – glopes

3

Genau das gleiche Problem hat eine Abhilfe beschrieben here.

FlattenHierarchie funktioniert nicht BTW. (nur auf statischen vars. sagt so in intellisense)

Workaround. Vorsicht vor Duplikaten.

1

das funktionierte gut und kurz und bündig für mich in einem Modell Binder benutzerdefinierten MVC. Sollte jedoch in der Lage sein, auf irgendein Reflexionsszenario zu extrapolieren. Noch stinkt Art, dass es zu

Pass
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) 
42

Type.GetInterfaces die abgeflachte Hierarchie gibt, so gibt es für eine rekursive Abstieg keine Notwendigkeit.

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

Dies sollte definitiv die richtige Antwort sein! Keine Notwendigkeit für die klobige Rekursion. – glopes

+0

Solide Antwort danke. Wie können wir Wert einer Eigenschaft in der Basisschnittstelle erhalten? –

+1

@ilkerunal: Der übliche Weg: Rufen Sie ['GetValue'] (https://msdn.microsoft.com/en-us/library/hh194385%28v=vs.110%29.aspx) auf der abgerufenen' PropertyInfo' auf, Übergeben Sie Ihre Instanz (deren Eigenschaftswert zu erhalten) als Parameter. Beispiel: 'var list = new [] {'a', 'b', 'c'}; var count = typeof (IList) .GetPublicProperties(). Zuerst (i => i.Name == "Count"). GetValue (list); '← gibt 3 zurück, obwohl" Count "in" ICollection "definiert ist. nicht "IList". – Douglas

0

Als Reaktion auf @douglas und @ user3524983 sollte die folgenden die Frage des OP beantworten:

kann das gesamte Verfahren unter Verwendung von LINQ prägnant viel mehr geschrieben werden

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

OK nächsten Mal werde ich es debuggen, bevor Sie buchen statt nach :-)