2011-01-09 14 views
11

Das Gleiche gilt für Methoden zu:Wie kann ich zwei PropertyInfos oder Methoden zuverlässig vergleichen?

mir gegeben wird zwei Instanzen von Property oder Methoden, die aus der Klasse extrahiert wurden sie sitzen auf über GetProperty() oder GetMember() etc, (oder von einem Member vielleicht).

Ich will, um zu bestimmen, ob sie in der Tat auf die gleichen oder die gleiche Methode so

(propertyOne == propertyTwo) 

oder

(methodOne == methodTwo) 

Klar, dass nicht wirklich zu gehen arbeiten, könnten Sie auf die gleiche Eigenschaft schauen, aber es könnte aus verschiedenen Ebenen der Klassenhierarchie extrahiert worden sein (in diesem Fall propertyOne != propertyTwo)

Of cours e, konnte ich bei DeclaringType aussehen und Wiederanforderung die Eigenschaft, aber das beginnt ein wenig verwirrend bekommen, wenn Sie anfangen zu denken über

  • Eigenschaften/Methoden auf Interfaces deklariert und
  • Eigenschaften/Methoden auf Klassen implementiert erklärt auf einer Basisklasse (virtuell) und außer Kraft gesetzt auf abgeleitete Klassen
  • Eigenschaften/Methoden auf einer Basisklasse deklariert, mit ‚neuen‘ außer Kraft gesetzt (in IL Welt ist dies nichts besonderes iirc)

am Ende der Tag, ich möchte nur in der Lage sein, eine intelligente eq zu tun Ich bin zu 80% sicher, dass die oben genannten Aufzählungspunkte nicht alle Randfälle abdecken, und während ich mich einfach hinsetzen konnte, schreibe eine Menge Tests und fange an zu spielen. Ich bin mir bewusst, dass mein Low-Level-Wissen, wie diese Konzepte tatsächlich umgesetzt werden, nicht ausgezeichnete ist, und ich hoffe, dass dies ein bereits beantwortet Thema ist und ich nur bei der Suche saugen.

Die beste Antwort wäre mir ein paar Methoden geben, die oben und erklärt, welche Grenzfälle kümmern und warum :-)


Klärung genommen erreichen wurden:

wahrsten Sinne des Wortes, ich möchte sicherstellen, dass sie die gleiche Eigenschaft sind, sind hier einige Beispiele

public interface IFoo 
{ 
    string Bar { get; set; } 
} 

public class Foo : IFoo 
{ 
    string Bar { get; set; } 
} 

typeof(IFoo).GetProperty("Bar") 

und

typeof(Foo).GetProperty("Bar") 

Werden zwei Eigenschaft Infos zurückkehren, die nicht gleich sind:

public class BaseClass 
{ 
    public string SomeProperty { get; set ; } 
} 

public class DerivedClass : BaseClass { } 


typeof(BaseClass).GetMethod("SomeProperty") 

und

typeof(DerivedClass).GetProperty("SomeProperty") 

kann ich nicht wirklich daran erinnern, ob diese beiden Rückkehr gleich Objekte jetzt, aber in meiner Welt Sie sind gleich.

Ähnlich:

public class BaseClass 
{ 
    public virtual SomeMethod() { } 
} 

public class DerivedClass 
{ 
    public override SomeMethod() { } 
} 

typeof(BaseClass).GetMethod("SomeMethod") 

und

typeof(DerivedClass).GetProperty("SomeMethod") 

Auch hier werden diese nicht überein - aber ich möchte, dass sie (ich weiß, sie sind nicht spezifisch gleich, aber in meiner Domain sie sind weil sie sich auf die gleiche ursprüngliche Eigenschaft beziehen)

Ich könnte es strukturell tun, aber das wäre "falsch".

Weitere Hinweise:

Wie beantrage Sie auch die Eigenschaft, dass eine andere Eigenschaft versteckt? Scheint eine meiner früheren Annahmen war ungültig, dass die Standardimplementierung von GetProperty("name") standardmäßig auf die aktuelle Ebene verweisen würde.

BindingFlags.DeclaringType erscheint nur am Ende Null zurück!

+1

"Eigenschaften/Methoden, die für eine Basisklasse deklariert sind, überschrieben mit 'neu'" - Das heißt eigentlich * verstecken *, und sie sind * sicherlich * verschiedene Mitglieder. Es macht keinen Sinn, sie als "gleich" zu betrachten. – Ani

+0

Nun, offensichtlich habe ich das nur schlecht formuliert - es wäre sehr schwer, Vergleichscode zu schreiben, um in diesem Fall wahr zu sein ;-) - es ist immer noch ein unglücklicher Pfad, der getestet werden muss, da ich strukturell vergleichenden Code denken kann . –

+1

Können Sie die Frage besser klären? Willst du 'string.GetHashCode == int.GetHashCode' (weil ihre Basisdefinitionen vom selben Typ sind -' object'). Wie wäre es mit 'List .Count' vs.' HashSet .Count' (beide implementieren 'ICollection .Count')? – Ani

Antwort

3

einen Blick auf die PropertyInfo Objekte aus IFoo/Foo Beispiel nehmen, können wir diese Schlüsse zu ziehen:

  1. Es gibt keine direkte Möglichkeit, die Eigenschaft, welche Klasse/Schnittstelle zu sehen, wurde zunächst erklärt auf.
  2. Um zu überprüfen, ob die Eigenschaft tatsächlich für eine Vorgängerklasse deklariert wurde, müssen wir über die Vorfahren iterieren und sehen, ob die Eigenschaft auch für sie existiert.
  3. Gleiches gilt für Schnittstellen, wir müssen Type.GetInterfaces aufrufen und von dort arbeiten. Vergessen Sie nicht, dass Interfaces andere Interfaces implementieren können, das muss rekursiv sein.

Also lassen Sie uns einen Riss haben. Zuerst vererbten Eigenschaften zu decken:

PropertyInfo GetRootProperty(PropertyInfo pi) 
{ 
    var type = pi.DeclaringType; 

    while (true) { 
     type = type.BaseType; 

     if (type == null) { 
      return pi; 
     } 

     var flags = BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Instance | 
        BindingFlags.Public | BindingFlags.Static; 
     var inheritedProperty = type.GetProperty(pi.Name, flags); 

     if (inheritedProperty == null) { 
      return pi; 
     } 

     pi = inheritedProperty; 
    } 
} 

Nun Eigenschaften in Interfaces deklariert zu decken (Suche mit DFS):

PropertyInfo GetImplementedProperty(PropertyInfo pi) 
{ 
    var type = pi.DeclaringType; 
    var interfaces = type.GetInterfaces(); 

    if (interfaces.Length == 0) { 
     return pi; 
    } 

    var flags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public; 
    var query = from iface in interfaces 
       let implementedProperty = iface.GetProperty(pi.Name, flags) 
       where implementedProperty != pi 
       select implementedProperty; 

    return query.DefaultIfEmpty(pi).First(); 
} 

diese zusammen binden:

PropertyInfo GetSourceProperty(PropertyInfo pi) 
{ 
    var inherited = this.GetRootProperty(pi); 
    if (inherited != pi) { 
     return inherited; 
    } 

    var implemented = this.GetImplementedProperty(pi); 
    if (implemented != pi) { 
     return implemented; 
    } 

    return pi; 
} 

Das sollte funktionieren. Indexierte Eigenschaften mit dem gleichen Namen, aber unterschiedlichen Typen und/oder Nummern von Indexierungsparametern werden nicht berücksichtigt, so dass die sprichwörtliche Übung für den Leser bleibt.

Haftungsausschluss: Ich habe dies nicht einmal kompiliert (keine Zeit, um Tests jetzt auszuführen). Es ist als Ausgangspunkt für "die" Antwort gedacht, da es bisher keine gibt.

+0

Immer noch ein ausgezeichneter Start - ich habe eine Reihe von Tests und einen Haufen von meinen (wie noch nicht funktionierender aber ähnlicher) Code, um es zu testen - ich werde deinen Beitrag lesen und sehen, wo die Unterschiede liegen und zu dir zurück :-) –

+0

Bah, so weit so gut aber es gibt einen Unterschied zwischen Mono und MS.NET also ich kann nicht vollständig verifizieren (nicht in der Lage, Tests auf meiner Windows VM aus irgendeinem Grund auszuführen) - Ich werde es funktionieren und Ihre Antwort akzeptieren, sobald ich die Randfälle verifiziert habe –

+0

Okay, also fast da - FYI Es ist nicht gut genug einen GetP machen Eigenschaft, müssen wir die Eigenschaft mit der InterfaceMap übereinstimmen - anders als das scheint bisher koscher –

1

Es scheint einfacher zu sein, die Deklarationstypen der zwei MemberInfo s zu überprüfen, die Sie vergleichen möchten. Im Falle einer Base/Subclass-Beziehung sollten sie die gleiche Deklaration darstellen, wenn die Deklarationstypen gleich sind.Im Falle von Schnittstellen, sollten sie die gleiche sein, wenn die Deklaration Interface-Typ in der Liste der Schnittstellen des anderen ist:

Type type1 = methodInfo1.DeclaringType; 
Type type2 = methodInfo2.DeclaringType; 

bool same = type1 == type2 || 
    type1.IsInterface && type2.GetInterfaces.Contains(type1) || 
    type2.IsInterface && type1.GetInterfaces.Contains(type2); 

Eine Sache der für Schnittstellen bewusst zu sein, ist das ‚Interface-Mapping‘ - Type.GetInterfaceMap was bedeutet, dass Methoden, die in einer Schnittstelle deklariert sind, in der implementierenden Klasse möglicherweise nicht den gleichen Namen haben, was Ihr aktueller Ansatz anscheinend nicht berücksichtigt.

+0

Dies wird immer noch falsch, wenn eines der Mitglieder überschreibt ein Basismitglied (in meiner Definition sind sie das gleiche), und natürlich wird wahr zurückgegeben, wenn die beiden Typen völlig verschieden sind, aber die gleichen Typen haben - zugegebenermaßen ist dies nur ein Tippfehler, weil Sie dies in eine Antwort abgeladen haben: -) - Punkt genommen, aber das ist in der Tat sehr einfach –

+1

Scheint mir, dass die längere Lösung die beste ist - finden Sie die Wurzel von BOTH-Mitgliedern, und vergleichen Sie diese, sich auf den Vergleich von memberOne/memberTwo direkt überhaupt scheint fehleranfällig –

2

Ich bin nicht genau sicher, wofür Sie das brauchen, aber ich denke, dass Ihre Definition von Gleichheit in diesem Fall lautet "machen die zwei Methodeninfos dieselbe Methode, wenn sie aufgerufen werden"? Wenn ja, dann müssen Sie wirklich zu einer Art im Vergleich angeben, zum Beispiel bedenken Sie:

public interface IFoo 
{ 
    void AMethod(); 
} 

public interface IBar 
{ 
    void AMethod(); 
} 

public class FooBar : IFoo, IBar 
{ 
    void AMethod(); 
} 

Klar typeof (IFoo) .GetMethod ("AMethod") und typeof (IBar) .GetMethod ("AMethod") sind nicht gleich, sie sind nicht einmal verwandt, ABER sie rufen dieselbe Methode auf einer Instanz von FooBar auf. Also, was Sie vielleicht gefallen ist ein Vergleich Methode, die drei Argumente verwendet:

bool WillInvokeSameMethodOnType(MethodInfo method1, MethodInfo method2, Type type) 

Schauen Sie sich die Klasse MethodInfoManager in FakeItEasy, könnte es sein, was Sie wollen: http://code.google.com/p/fakeiteasy/source/browse/Source/FakeItEasy/Core/MethodInfoManager.cs?r=8888fefbc508fb02d5435a3e33774500bec498b3

+0

Making der verfügbare Typ wäre ziemlich schwierig, aber DeclaringType usw. wäre sicherlich genug? Ich denke, MethodInfoManager kommt nahe - aber wie verwalten Sie neu vs virtuell? –

+0

Nicht sicher, was genau du fragst, es gibt keinen speziellen Fall neu vs virtuell in meinem Fall, da alles, was mich interessiert, ist, ob die Methodeninfo die gleiche Methode versenden würde. –

2

So, das war ein harter Knochen und vor Ich gehe in langweilige Details Ich werde dies sagen, ich endete damit, nur einen strukturellen Vergleich zu machen, da der einzige Ort , der fallen wird, ist, wenn ein Mitglied verbirgt ein anderes Mitglied mit dem 'neuen' Schlüsselwort in C# - entschieden, dass dies ein kleines Problem in diesem System im Vergleich zu der Vielzahl anderer Verletzungen ist, dass eine angemessene Lösung für dieses Problem e nds up verursacht, wenn es schief geht (und es tut schief gehen, glaube mir).

ich eine Antwort oben akzeptiert, weil es nahe kam, das Problem zu lösen - das einzige Problem, es hat ist, dass es in der Natur noch strukturell bedingt ist (und ich kann das erreichen, indem Typ/name/Argument check)

Es gibt jedoch einige Unterschiede, hauptsächlich, dass es notwendig ist, die Interface Map zu betrachten, anstatt GetProperty aufzurufen - das ist ein ziemlich wichtiges Detail - hier ist meine überarbeitete Methode, die unter bestimmten Umständen umfallen wird.

private PropertyInfo GetImplementedProperty(PropertyInfo pi) 
    { 
     var type = pi.DeclaringType; 
     var interfaces = type.GetInterfaces(); 

     for(int interfaceIndex = 0; interfaceIndex < interfaces.Length; interfaceIndex++) 
     { 
      var iface = interfaces[interfaceIndex]; 
      var interfaceMethods = type.GetInterfaceMap(iface).TargetMethods; 

      MethodInfo matchingMethod = null; 
      for (int x = 0; x < interfaceMethods.Length; x++) 
      { 
       if (pi.GetGetMethod().LooseCompare(interfaceMethods[x]) || pi.GetSetMethod().LooseCompare(interfaceMethods[x])) 
       { 
        matchingMethod = type.GetInterfaceMap(iface).InterfaceMethods[x]; 
        break; 
       } 
      } 
      if (matchingMethod == null) continue; 

      var interfacePi = from i in interfaces 
           from property in i.GetProperties() 
           where property.GetGetMethod().LooseCompare(matchingMethod) || property.GetSetMethod().LooseCompare(matchingMethod) 
           select property; 

      return interfacePi.First(); 
     } 

     return pi; 
    } 

Ich landete Aufgeben auf zu prüfen, ob ein Mitglied ein anderes Mitglied versteckt hielt, und ging für den folgenden Hack:

private PropertyInfo GetRootProperty(PropertyInfo pi) 
    { 
     if ((pi.GetGetMethod().Attributes & MethodAttributes.Virtual) != MethodAttributes.Virtual) { return pi; } 

     var type = pi.DeclaringType; 

     while (true) 
     { 
      type = type.BaseType; 

      if (type == null) 
      { 
       return pi; 
      } 

      var flags = BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Instance | 
         BindingFlags.Public | BindingFlags.Static; 

      var inheritedProperty = type.GetProperty(pi.Name, flags); 

      if (inheritedProperty == null) 
      { 
       return pi; 
      } 

      pi = inheritedProperty; 
     } 
    } 

ich eine Annahme machen hier, dass eine Eigenschaft/Methode, um die ‚neue Verwendung 'keyword wird das virtuelle Schlüsselwort auch nicht verwenden, da' neu 'sowieso ein bisschen ein Randfall ist, ist das ziemlich unwahrscheinlich.

Dies ist so weit wie ich vor der Entscheidung, dass es meine Tests bestanden hat und ich war damit zufrieden. (Und bevor ich mich dazu entschloss, mich nur für einen strukturellen Check zu entscheiden ...) Ich hoffe, dass dies für jeden nützlich ist, der in Zukunft auf diese Weise stolpert.

Verwandte Themen