Les wrappers doivent-ils se comparer comme égaux en utilisant l'opérateur == lorsqu'ils enveloppent le même objet?

19

J'écris un wrapper pour les éléments XML qui permet à un développeur d'analyser facilement les attributs du XML. L'encapsuleur n'a aucun état autre que l'objet encapsulé.

J'envisage l'implémentation suivante (simplifiée pour cet exemple) qui inclut une surcharge pour l' ==opérateur.

class XmlWrapper
{
    protected readonly XElement _element;

    public XmlWrapper(XElement element)
    {
        _element = element;
    }

    public string NameAttribute
    {
        get
        {
            //Get the value of the name attribute
        }
        set
        {
            //Set the value of the name attribute
        }
    }

    public override bool Equals(object other)
    {
        var o = other as XmlWrapper;
        if (o == null) return false;
        return _element.Equals(o._element);
    }

    public override int GetHashCode()
    {
        return _element.GetHashCode();
    }

    static public bool operator == (XmlWrapper lhs, XmlWrapper rhs)
    {
        if (ReferenceEquals(lhs, null) && ReferenceEquals(rhs, null)) return true;
        if (ReferenceEquals(lhs, null) || ReferenceEquals(rhs, null)) return false;

        return lhs._element == rhs._element;
    }

    static public bool operator != (XmlWrapper lhs, XmlWrapper rhs)
    {
        return !(lhs == rhs);
    }
}

Si je comprends bien c # idiomatique, l' ==opérateur est pour l'égalité de référence tandis que la Equals()méthode est pour l'égalité des valeurs. Mais dans ce cas, la "valeur" n'est qu'une référence à l'objet encapsulé. Je ne sais donc pas ce qui est conventionnel ou idiomatique pour c #.

Par exemple, dans ce code ...

var underlyingElement = new XElement("Foo");
var a = new XmlWrapper(underlyingElement);
var b = new XmlWrapper(underlyingElement);

a.NameAttribute = "Hello";
b.NameAttribute = "World";

if (a == b)
{
    Console.WriteLine("The wrappers a and b are the same.");
}

.... le programme devrait-il afficher "Les enveloppes a et b sont les mêmes"? Ou serait-ce étrange, c'est-à-dire violer le principe du moindre étonnement ?

John Wu
la source
Pour toutes les fois où j'ai dépassé, Equalsje n'ai jamais dépassé ==(mais jamais l'inverse). Le paresseux est-il idiomatique? Si j'obtiens un comportement différent sans un casting explicite qui viole le moins d'étonnement.
radarbob
La réponse à cela dépend de ce que fait le NameAttribute - modifie l'élément sous-jacent? Est-ce une donnée supplémentaire? La signification de l'exemple de code (et s'il doit être considéré comme égal) change en fonction de cela, donc je pense que vous devez le remplir.
Errorsatz
@Errorsatz Je m'excuse, mais je voulais garder l'exemple concis, et j'ai supposé qu'il était clair qu'il modifierait l'élément encapsulé (en particulier en modifiant l'attribut XML nommé "nom"). Mais les détails importent à peine - le point est que l'encapsuleur autorise l'accès en lecture / écriture à l'élément encapsulé mais ne contient aucun état propre.
John Wu
4
Eh bien, dans ce cas, ils importent - cela signifie que l'affectation "Bonjour" et "Monde" est trompeuse, car cette dernière remplacera la première. Ce qui signifie que je suis d'accord avec la réponse de Martin selon laquelle les deux peuvent être considérés comme égaux. Si le NameAttribute était réellement différent entre eux, je ne les considérerais pas comme égaux.
Errorsatz
2
"Si je comprends bien le c # idiomatique, l'opérateur == est pour l'égalité de référence tandis que la méthode Equals () est pour l'égalité des valeurs." Est-ce bien? La plupart du temps, j'ai vu == surchargé pour l'égalité des valeurs. L'exemple le plus important étant System.String.
Arturo Torres Sánchez, le

Réponses:

17

Étant donné que la référence à l'emballage XElementest immuable, il n'y a pas de différence observable extérieurement entre deux instances de XmlWrappercet emballage le même élément, il est donc logique de surcharger ==pour refléter ce fait.

Le code client se soucie presque toujours de l'égalité logique (qui, par défaut, est implémentée en utilisant l'égalité de référence pour les types de référence). Le fait qu'il y ait deux instances sur le tas est un détail d'implémentation dont les clients ne devraient pas se soucier (et ceux qui le feront utiliser Object.ReferenceEqualsdirectement).

casablanca
la source
9

Si vous pensez que cela a le plus de sens

La question et la réponse sont une question d' attente du développeur , ce n'est pas une exigence technique.

SI vous considérez qu'un wrapper n'a pas d'identité et qu'il doit être défini uniquement par son contenu, alors la réponse à votre question est oui.

Mais c'est un problème récurrent. Deux wrappers doivent-ils montrer l'égalité lorsqu'ils enveloppent des objets différents mais avec les deux objets ayant exactement le même contenu?

La réponse se répète. SI les objets de contenu n'ont pas d'identité personnelle et sont plutôt purement définis par leur contenu, alors les objets de contenu sont en fait des wrappers qui présenteront l'égalité. Si vous encapsulez ensuite les objets de contenu dans un autre wrapper, ce wrapper (supplémentaire) doit alors également présenter l'égalité.

C'est des tortues tout le long .


Conseil général

Chaque fois que vous vous écartez du comportement par défaut, cela devrait être explicitement documenté. En tant que développeur, je m'attends à ce que deux types de référence ne présentent pas l'égalité même si leur contenu est égal. Si vous changez ce comportement, je vous suggère de le documenter clairement afin que tous les développeurs soient conscients de ce comportement atypique.


Si je comprends bien c # idiomatique, l' ==opérateur est pour l'égalité de référence tandis que la Equals()méthode est pour l'égalité des valeurs.

C'est son comportement par défaut, mais ce n'est pas une règle inamovible. C'est une question de convention, mais les conventions peuvent être modifiées lorsque cela se justifie .

stringest un excellent exemple ici, tout comme ==un contrôle d'égalité de valeur (même lorsqu'il n'y a pas de chaîne interne!). Pourquoi? En termes simples: car avoir des chaînes se comportent comme des objets de valeur semble plus intuitif pour la plupart des développeurs.

Si votre base de code (ou la vie de vos développeurs) peut être considérablement simplifiée en faisant en sorte que vos wrappers présentent une égalité de valeur à tous les niveaux, allez-y (mais documentez-le ).

Si vous n'avez jamais besoin de vérifications d'égalité de référence (ou qu'elles sont rendues inutiles par votre domaine d'activité), il est inutile de conserver une vérification d'égalité de référence. Il est préférable de le remplacer par une vérification d'égalité des valeurs afin d' éviter les erreurs de développeur .
Cependant, sachez que si vous avez besoin de vérifications d'égalité de référence plus tard, la réimplémentation peut prendre un effort notable.

Flater
la source
Je suis curieux de savoir pourquoi vous vous attendez à ce que les types de référence ne définissent pas l'égalité de contenu. La plupart des types ne définissent pas l'égalité simplement parce qu'elle n'est pas essentielle à leur domaine, pas parce qu'ils veulent l' égalité de référence.
casablanca
3
@casablanca: Je pense que vous interprétez une définition différente de "attendre" (c.-à-d. exigence contre hypothèse). Sans documentation, j'attends (c'est-à-dire supposons) que ==vérifie l'égalité de référence car c'est le comportement par défaut. Cependant, si ==vérifie réellement l'égalité des valeurs, je m'attends (c'est-à-dire exige) que cela soit documenté explicitement. I'm curious why you expect that reference types won't define content equality.Ils ne le définissent pas par défaut , mais cela ne signifie pas que cela ne peut pas être fait. Je n'ai jamais dit que cela ne pouvait pas (ou ne devait pas) être fait, je ne m'y attendais pas (c'est-à-dire l'assumais) par défaut.
Flater
Je vois ce que tu veux dire, merci pour la clarification.
casablanca
2

Vous comparez essentiellement des chaînes, donc je serais étonné que deux wrappers contenant le même contenu XML ne soient pas considérés comme égaux, que ce soit vérifié en utilisant Equals ou ==.

La règle idiomatique peut avoir un sens pour les objets de type référence en général, mais les chaînes sont spéciales dans un sens idiomatique, vous êtes censé les traiter et les considérer comme des valeurs bien que techniquement ce soient des types de référence.

Votre postfixe Wrapper ajoute cependant de la confusion. Il dit essentiellement "pas un élément XML". Dois-je donc le traiter comme un type de référence après tout? Sémantiquement, cela n'aurait aucun sens. Je serais moins confus si la classe était nommée XmlContent. Cela signifierait que nous nous soucions du contenu, et non des détails techniques de mise en œuvre.

Martin Maat
la source
Où mettriez-vous la limite entre "un objet construit à partir d'une chaîne" et "essentiellement une chaîne"? Cela semble une définition assez ambiguë du point de vue de l'API externe. L'utilisateur de l'API doit-il réellement deviner les éléments internes pour deviner le comportement?
Arthur Havlicek
@Arthur La sémantique de la classe / de l'objet devrait fournir l'indice. Et parfois, ce n'est pas si évident, d'où cette question. Pour les types à valeur réelle, cela sera évident pour tout le monde. Pour les vraies cordes aussi. Le type doit en fin de compte indiquer si une comparaison doit impliquer un contenu ou une identité d'objet.
Martin Maat