2009-04-27 7 views
4

Ich habe eine C# -Klasse mit einem Zeichenfolgenelement definiert. Für alle Absichten ein Zweck, denke an diese Klasse als eine Unterklasse von String (außer das ist nicht erlaubt). Ich benutze es, um ein stark typisiertes Zeichenkettenfeld darzustellen, das einem bestimmten Format entspricht (ich habe es erheblich vereinfacht).Überschreiben Entspricht und vergleicht mit Zeichenfolge

public class field 
{ 
    private readonly string m_field; 
    public field(string init_value) 
    { 
     //Check the syntax for errors 
     if (CheckSyntax(init_value)) 
     { 
      m_field = init_value; 
     } 
     else 
     { 
      throw new ArgumentOutOfRangeException(); 
     } 
    } 

    public override string ToString() 
    { 
     return m_field; 
    } 
} 

Nun, ich mag diese Klasse kann auf jede beliebige andere Zeichenfolge (Objekt oder wörtliche) direkt miteinander vergleichen. Deshalb habe ich die folgenden in der Klasse implementiert:

public override bool Equals(object obj) 
{ 
    if (obj == null) 
    { 
     return false; 
    } 

    return this.m_field == obj.ToString(); 
} 

public override int GetHashCode() 
{ 
    return this.m_field.GetHashCode(); 
} 

public static bool operator ==(field x, Object y) 
{ 
    if ((object)x == null && y == null) 
    { 
     return true; 
    } 
    else if ((object)x == null || y == null) 
    { 
     return false; 
    } 
    else 
    { 
     return (x.m_field == y.ToString()); 
    } 
} 

public static bool operator !=(field x, Object y) 
{ 
    return !(x == y); 
} 

Nun, wenn ich einen Komponententest ich schreibe, abhängig von der Reihenfolge, die ich in den Argumenten zu Assert.AreEqual bin vorbei, erhalte ich unterschiedliche Ergebnisse:

Ich nehme an, dies liegt daran, in der ersten Assert, ruft es field.Equals() und in der zweiten ruft es String.Equals(). Offensichtlich nähere ich mich dem aus dem falschen Winkel. Kann mir jemand einen Einblick geben?

Eine andere Sache. Ich kann hier keine Struktur (Werttyp) verwenden, da ich das in meinem tatsächlichen Fall in einer Basisklasse definiere und davon erben.

Antwort

5

Dies ist im Detail in Effective Java als Artikel 8 beschrieben: Befolgen Sie die allgemeinen Vertrags wenn equals überschrieben.

Die Methode equals implementiert eine Äquivalenzrelation.

Es ist reflexiv, symmetrisch, Transitive, konsistent und für alle Nicht-NULL-Verweis x, x.equals(null) muss false zurück. Das genannte Beispiel, um die Symmetrie zu brechen, ist ähnlich wie bei Ihnen.

field Klasse ist sich dessen bewusst string Klasse, aber die eingebaute in string Klasse ist nicht bekannt, field. Dies ist eine einseitige Interoperabilität und sollte entfernt werden.

+0

+1 - sehr klar, danke! –

9

Grundsätzlich können Sie nicht tun, was Sie wollen - es gibt keine Möglichkeit, string Ihre Klasse für Gleichstellungszwecke zu erkennen. Sie werden es niemals reflexartig machen können - Sie werden es niemals in die Lage versetzen, dem Vertrag von object.Equals zu folgen.

Ich würde persönlich versuchen, es neu zu gestalten, so dass Sie nicht die Validierung als Teil des Typs selbst haben - machen Sie es Teil der relevanten Eigenschaften der Geschäftseinheiten (oder was auch immer sie sind).

+0

+1 Typen wie diese sind knifflig zu verwenden und schwierig, andere konsistent zu verwenden. Es ist besser, die Validierung und andere Geschäftslogik in der Entität selbst zu behalten. –

+0

Leider verwende ich Instanzen dieser Klasse als Schlüssel in Wörterbüchern und in Sammlungen und so weiter. Ich brauche Equals() und GetHashCode(), um sich wie Wertetypen zu verhalten, damit sie in diesen Situationen korrekt funktionieren. –

+0

Überschreiben Sie die Equals(), und Sie können in Betracht ziehen, IComparable zu implementieren http://msdn.microsoft.com/en-us/library/system.icomparable.aspx Ich wünschte wirklich, MS würde Operator aus C# überladen übernehmen. –

4

würde ich jemand mit Ihrem Feldklasse implizit als String entmutigen, und zwingen diese Art der Nutzung:

string valid = "Some String"; 
field target = new field(valid); 
Assert.AreEqual(target.toString(), valid); 
Assert.AreEqual(valid, target.toString()); 
0

Basierend auf jeden Feedback und meine eigenen Bedürfnisse, hier ist was nach vorne als eine mögliche Lösung Ich stelle (Ich bin die Equals-Methode wie folgt zu ändern):

public override bool Equals(Object obj) 
{ 
    if (obj == null) 
    { 
     return false; 
    } 

    field f = obj as field; 
    if (f != null) 
    { 
     return this == f; 
    } 
    else 
    { 
     return obj.Equals(this); 
    } 
} 

Dies scheint seine richtige zu ermöglichen Verwendung in Dictionary- und Auflistungsklassen, die auf den Equals- und GetHashCode-Methoden beruhen, um festzustellen, ob der Wert bereits vorhanden ist.

Auch jetzt diese beide fehlschlagen:

string valid = "Some String"; 
field target = new field(valid); 
Assert.AreEqual(target, valid); // FAILS 
Assert.AreEqual(valid, target); // FAILS 

Und diese beiden passieren:

string valid = "Some String"; 
field target = new field(valid); 
Assert.AreEqual(target.ToString(), valid); // PASSES 
Assert.AreEqual(valid, target.ToString()); // PASSES 

Und diese beiden passieren:

field f1 = new field("Some String"); 
field f2 = new field("Some String"); 
Assert.AreEqual(f1, f2); // PASSES 
Assert.AreEqual(f2, f1); // PASSES 
+0

Das ist mehr oder weniger der Standard-Equals-Operator, so dass das Überschreiben nicht die äquivalente Funktionalität ergibt. Da Ihre Felder Textdaten darstellen, könnten Sie stattdessen "return this.text == f.text" verwenden. Aber in diesem Fall müssen Sie GetHashCode auf etwas wie return this.text.GetHashCode() überschreiben, das sicherstellen würde, dass sich Auflistungsklassen gut verhalten. –

+0

Ja, wenn Sie sich die ursprüngliche Frage ansehen, hat sie eine Überschreibung für GetHashCode(). –

+0

das wird nicht funktionieren ... die kanonische Equals-Implementierung hat eine Überprüfung wie wenn this.GetType()! = Arg.GetType() false zurückgibt. Also ein "Wert" .Equals (fieldObj) wird zwangsläufig fehlschlagen. Kein Weg um ihn herum. – Gishu

0

Dies ist String # Equals

public override bool Equals(object obj) 
{ 
    string strB = obj as string; 
    if ((strB == null) && (this != null)) 
    { 
     return false; 
    } 
    return EqualsHelper(this, strB); 
} 

Liefern eines anderen Arguments als String an String # Equals wird false zurückgeben. Ich würde ein "Umdenken" vorschlagen, um das zu umgehen.

+0

Auf einer Randnotiz, warum das (this! = Null) - Ist dies nicht garantiert innerhalb einer Instanzmethode nicht null? – Gishu

0

Ich empfehle die Verwendung object.ReferenceEquals(), wenn Sie intern versuchen zu überprüfen, ob x oder y null ist.

public static bool operator ==(field x, Object y) 
{ 
    if (object.ReferenceEquals(x, null) && object.ReferenceEquals(y, null)) 
    { 
     return true; 
    } 
    else if (object.ReferenceEquals(x, null) || object.ReferenceEquals(y, null)) 
    { 
     return false; 
    } 
    else 
    { 
     return (x.m_field == y.ToString()); 
    } 
} 
Verwandte Themen