Laden...

Ermitteln, ob in einer Klasse eine Methode überschrieben wurde

Letzter Beitrag vor 14 Jahren 11 Posts 3.133 Views
Ermitteln, ob in einer Klasse eine Methode überschrieben wurde

Hi,

es geht um folgendes Problem:
Ich schreibe mir zur Zeit einen Equals-Builder. Dieser geht einfach alle Props durch, die er lesen kann, und vergleicht diese mittels Equals. Das Problem: Wie finde ich mittels Reflection heraus, ob die Propery-Klasse Equals überschrieben hat?

Ich hatte dazu folgenden Ansatz:
(PI = Property-Info-Objekt)


if (pi.PropertyType.GetMethod("Equals", BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly) == null) 
{
  continue; //Für Schleife mit allen Props
}

Das geht aber nicht, da immer NULL rauskommt.

Wisst ihr, wie es geht?

Hallo KleinerHacker,

Das geht aber nicht, da immer NULL rauskommt.

Ganz wichtig (ich habe es gerade extra nochmal in [Artikel] Reflection und Metaprogrammierung besonders hervorgehoben):
Bei der Verwendung von BindingFlags müssen immer sowohl Zugriffsmodifizier (BindingFlags.Public, BindingFlags.NonPublic, etc.) als auch BindingFlags.Static und/oder BindingFlags.Instance angeben werden! Sonst erhält man nur leere Listen bzw. null zurück.

Das Problem: Wie finde ich mittels Reflection heraus, ob die Propery-Klasse Equals überschrieben hat?

Du kannst dir auch einfach so (ohne BindingFlags) die PropertyInfos ermitteln und dann mittels Type.DeclaringType gucken, welcher Typ (in der Vererbungshierarchie) das Property deklariert/zuletzt überschrieben hat.

Gruß,
dN!3L

Hallo KleinerHacker,

Das Problem: Wie finde ich mittels Reflection heraus, ob die Propery-Klasse Equals überschrieben hat?

Was mir da als Frage in den Sinn kommt, warum möchtest Du das überhaupt überprüfen?

Wenn ich ein "Equals" überschreibe, dann hat das normalerweise einen Grund, nämlich das ein "herkömmliches" Equal mir ein anderes Verhalten liefert als das ich wünsche. Deshalb ist es auch legitim "Equals" zu überschreiben.

Und deshalb verstehe ich nicht, warum es für Dich wichtig ist, dass "Equals" überschrieben wurde.

Grüße
Norman-Timo

A: “Wie ist denn das Wetter bei euch?”
B: “Caps Lock.”
A: “Hä?”
B: “Na ja, Shift ohne Ende!”

Ähm, naja, im Normal-Zustand ist das Equals, wenn es von Object geerbt ist, nur ein Object.ReferenceEquals. Ich will ja nicht die Referenzen überprüfen, sondern die Gleichheit der Objekte selbst.
Zudem ist es so, dass ohnehin jede Klasse Equals überschreiben sollte (und GetHashCode). Insbesondere, wenn diese Klasse aus dem Framework stammt und daher nicht mehr veränderbar ist.

Ganz wichtig (ich habe es gerade extra nochmal in [Artikel] Reflection und Metaprogrammierung besonders hervorgehoben):
Bei der Verwendung von BindingFlags müssen immer sowohl Zugriffsmodifizier (BindingFlags.Public, BindingFlags.NonPublic, etc.) als auch BindingFlags.Static und/oder BindingFlags.Instance angeben werden! Sonst erhält man nur leere Listen bzw. null zurück.

Das habe ich auch schon probiert:


if (pi.PropertyType.GetMethod("Equals", BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance) == null)
{
  continue; //Für Schleife mit allen Props
}

Dann erhalte ich aber eine Exception mit der Meldung:
Mehrdeutige Übereinstimmung gefunden!

Das geht auch nicht...

Du kannst dir auch einfach so (ohne BindingFlags) die PropertyInfos ermitteln und dann mittels
>
gucken, welcher Typ (in der Vererbungshierarchie) das Property deklariert/zuletzt überschrieben hat.

Das bringt mir ja nichts, dass weiß ich ohnehin schon.
Hier mal die Klasse, in der ich das Brauche (ist auskommentiert):


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Tools
{
    /// <summary>
    /// Tools für die einfache Behandlung von HashCodes und Equals
    /// </summary>
    public static class DefaultEHTools
    {
        /// <summary>
        /// Erzeugt ein Standard-Equals
        /// </summary>
        /// <typeparam name="T">Typ des zu vergleichenden Objektes</typeparam>
        /// <param name="thisObj">Das This-Objekt. <b>Hier immer <i><u>this</u></i> übergeben</b></param>
        /// <param name="obj">Das Objekt, welches übergeben wurde</param>
        /// <returns>TRUE, wenn absolut gleich</returns>
        public static bool CreateEquals<T>(T thisObj, object obj) where T : class
        {
            //Standard-Test
            if (obj == null)
                return false;
            else if (!(obj is T))
                return false;
            else if (Object.ReferenceEquals(thisObj, obj))
                return true;

            //Umwandlung
            T myObj = obj as T;

            //Ermittlung der Properties
            PropertyInfo[] props = typeof(T).GetProperties();
            foreach (PropertyInfo pi in props)
            {   //Überspringen, wenn Equals von der Property-Klasse nicht implementiert wurde
                /*if (pi.PropertyType.GetMethod("Equals", BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public) == null)
                {
                    Console.WriteLine("Overjump: " + pi.Name + " (" + pi.PropertyType.Name + ")");
                    continue;
                }*/

                //Lesbar?
                if (pi.CanRead)
                {   //Inhalte ermitteln
                    object value1 = pi.GetValue(thisObj, null);
                    object value2 = pi.GetValue(myObj, null);

                    //Standard-Vergleich bei NULL und nicht NULL
                    if (((value1 == null) && (value2 != null)) ||
                        ((value1 != null) && (value2 == null)))
                        return false;
                    //Vergleich mit Equals, wenn Objekte nicht NULL
                    else if ((value1 != null) && (value2 != null))
                    {
                        if (!value1.Equals(value2))
                            return false;
                    }
                    //Sonst sind beide NULL, also beide gleich
                }
            }

            //Wenn überall durchgelaufen, alles OK
            return true;
        }

        /// <summary>
        /// Ermittelt den Standard-HashCode
        /// </summary>
        /// <typeparam name="T">Typ des zu vergleichenden Objektes</typeparam>
        /// <param name="thisObj">Das This-Objekt. <b>Hier immer <i><u>this</u></i> übergeben</b></param>
        /// <returns>HashCode-Wert</returns>
        public static int CreateHashCode<T>(T thisObj) where T : class
        {
            int res = 0;
            int prime = new Random().Next() % 25 + 5;

            PropertyInfo[] props = typeof(T).GetProperties();
            foreach (PropertyInfo pi in props)
            {   //Überspringen, wenn GetHashCode von der Property-Klasse nicht implementiert wurde
                /*if (pi.PropertyType.GetMethod("GetHashCode", BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public) == null)
                {
                    Console.WriteLine("Overjump: " + pi.Name + " ("+pi.PropertyType.Name+")");
                    continue;
                }*/
                
                //Lesbar?
                if (pi.CanRead)
                {   //Inhalte ermitteln
                    object value = pi.GetValue(thisObj, null);

                    //Wenn nicht NULL, dann HashCode ermitteln, sonst nicht
                    if (value != null)
                        res = res * prime + value.GetHashCode();
                    else
                        res = res * prime;
                }
            }

            //Rückgabe des Wertes
            return res;
        }
    }
}

Ich hoffe, dass hilft fürs bessere Verständnis...

Zudem ist es so, dass ohnehin jede Klasse Equals überschreiben sollte (und GetHashCode). Insbesondere, wenn diese Klasse aus dem Framework stammt und daher nicht mehr veränderbar ist.

Nein das ist blödsinn. In den meisten Fällen reicht die Standardimplementierung ReferenceEquals völlig aus. Zu dem ist ReferenceEquals um einiges schneller, als wenn du jedes Feld und derren Felder wieder vergleichst.

Wo Equals und GetHashCode überschrieben werden sollte, sind Werttypen. Grund, System.ValueType überschreibt Equals und GetHashCode und prüft alle Felder auf Gleichheit oder berechnet den HashCode für alle Felder mittels Reflection. Da dies recht langsam ist, sollten eben diese beiden Methoden in Werttypen überschrieben werden.

GetHashCode und Equals werden zu dem fasst ausschließlich in HashTable/Dictionaries verwendet. Um zwei Instanzen (egal ob Werttyp oder Referenztyp) miteinander zu vergleichen, wird der ==-Operator benutzt. Ist in der Klasse keiner definiert, wird automatisch Object.ReferenceEquals verwendet (zumindest C#, VB.NET machts mal wieder nicht so)

Aber nochmal, wichtig ist es zu unterscheiden, ob zwei Instanzen die selben sind (ReferenceEquals) oder ob der Inhalt gleich ist.

Außerdem ist die Überschreibung von Equals und GetHashCode nicht ganz trivial. Werden beispielsweise in Equals und GetHashCode die beiden Felder A und B verglichen/berechnet, müssen diese beide Felder immutable sein, d.h. keinerlei Änderungen nach dem Konstruktor dürfen zugelassen werden. Besonders bei Dictionaries können dort ansonsten derbe Probleme auftreten.

PS: Tip, fürs Dictionary, wenn dir Equals und GethashCode nicht passt, gibts immernoch das IEqualityComparer<TKEY>-Interface

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

Hallo KleinerHacker,

nochmal zu deinem Reflection-Problem: Du willst also wissen, ob die Methode Object.Equals überschrieben wurde. Dazu musst du erstmal das MethodInfo zu dieser Methode holen. Wie du ja schon bemerkt hast, gibt es manchmal mehrdeutige Übereinstimmungen (wenn Equals überladen wurde), also musst du noch die Parametertypen mit angeben, damit die korrekte Equals-Methode gefunden wird. Wenn du nun das passende MethodInfo hast, kannst du mittels der DeclaringType-Eigenschaft gucken, ob es überschrieben wurde.

MethodInfo methodInfo = typeof(A).GetMethod("Equals",new Type[] { typeof(object) });
if (methodInfo.DeclaringType!=typeof(object))
{
   // Equals wurde überschrieben
}

Beim Suchen der MethodInfos sollte dir aber auch klar sein, dass z.B. IComparable<T> implementiert wurde - wodurch du dann noch nach anderen Equals-Methoden suchen müsstest (oder auch nicht).

Beste Grüße,
dN!3L

Ja, danke, dass hilft weiter.

@kleines_eichhoernchen:
Leider kann ich deiner Argumentation nicht folgen, da dies gegen die Vorschrift für Objekte verstößt, sofern diese Werte haben. Die Methode ReferenceEquals sollte immer über == verwendet werden. Equals ist für Wert-Vergleiche da. Der ==-Operator kann bei immutable-Objekten überschrieben werden. Equals kann aber auch bei veränderlichen Objekten verwendet werden.
Dazu folgender Link:
Richtlinien zum Überladen von Equals() und des ==-Operators (C#-Programmierhandbuch)

Equals ist für Wert-Vergleiche da

Nein, das passt nicht. Referenztypen sind standardmäßig keine Werttypen. Hat hingegen eine Klasse ein Werttypverhalten (beispielsweise String) - unabhängig davon, ob diese immutable ist oder nicht - macht es durchaus sinn, Equals und GetHashCode zu überschreiben. Bei Werttypen sollte Equals und GethashCode immer überschrieben werden.

Weiterhin macht es keinerlei Sinn, dass Klassen wie Stream, Array, XmlDocument, Controls, usw. standardmäßig den Inhalt vergleichen, das wäre grottenlangsam und zu 99% die falsche Implementierung.

Anders sieht es aus bei mehrfachabgeleiteten Klassen. Implementiert eine davon Equals und GetHashCode, sollten auch die davon abgeleiten Klassen diese Funktionen überschreiben. Aber generell kein muss.

Was ein Programmierer einer public-Klasse hingegen nie machen sollte, nachträglich GetHashCode oder Equals hinzufügen.

wenn du den Inhalt einer Klasse vergleichen möchtest und du weißt, diese überschreibt Equals nicht, kannst du dir immernoch ein Helferlein (IEqualityComparer oder statische Methode) schreiben.

Was auch gegen eine generelle Implementierung spricht, ein Entwickler einer Klasse kann nicht wissen, wie du als Benutzer der Klasse, den Inhalt verglichen haben möchtest. Oftmals ist für den Enduser Gleichheit eben nicht eine Prüfung über alle Felder, sondern je nach Anwendung eben nur über bestimmte Felder.

Und nochwas, Richtlinien sind Richtlinien und keine Gesetze.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

Ja, danke, dass hilft weiter.

@kleines_eichhoernchen:
Leider kann ich deiner Argumentation nicht folgen, da dies gegen die Vorschrift für Objekte verstößt, sofern diese Werte haben. Die Methode ReferenceEquals sollte immer über == verwendet werden. Equals ist für Wert-Vergleiche da. Der ==-Operator kann bei immutable-Objekten überschrieben werden. Equals kann aber auch bei veränderlichen Objekten verwendet werden.
Dazu folgender Link:

>

Was gemeint ist, ist folgendes:

Wenn Du == überlädst, solltest Du auch Equals und GetHashCode überschreiben. Also entweder alle oder nichts.

Wissensvermittler und Technologieberater
für .NET, Codequalität und agile Methoden

www.goloroden.de
www.des-eisbaeren-blog.de