ラッパーは、同じオブジェクトをラップするときに==演算子を使用して等しいと比較する必要がありますか?


19

開発者がXMLから属性を簡単に解析できるようにするXML要素のラッパーを書いています。ラッパーには、ラップされるオブジェクト以外の状態はありません。

==オペレーターのオーバーロードを含む次の実装(この例では簡略化)を検討しています。

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

慣用的なc#を理解しているように、==演算子は参照の平等のためであり、Equals()メソッドは値の平等のためです。ただし、この場合、「値」はラップされるオブジェクトへの単なる参照です。だから、私はc#の慣習的または慣用的なものが明確ではありません。

たとえば、このコードでは...

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.");
}

....プログラムは「ラッパーaとbは同じです」を出力する必要がありますか?それとも奇妙なことでしょう、すなわち、最小の驚きの原則に違反しますか?


いつEqualsまでも私はオーバーライドしました==(しかし、その逆はありません)。怠zyな慣用句ですか?最小限の驚きに違反する明示的なキャストなしで異なる動作を取得した場合。
レーダーボブ

これに対する答えは、NameAttributeが何をするかによって異なります-基本要素を変更しますか?追加のデータはありますか?サンプルコード(とそれが等しいと見なされるべきかどうか)の意味は、私はあなたがそれを埋めるために必要があると思うので、それに応じて変化します。
Errorsatz

@Errorsatz申し訳ありませんが、例を簡潔にしておきたいと思います(特に「name」という名前のXML属性を変更することで、ラップされた要素を変更することは明らかだと思いました)ラッパーはラップされた要素への読み取り/書き込みアクセスを許可しますが、独自の状態は含まれません。
ジョンウー

4
さて、この場合、それらは重要です-「Hello」と「World」の割り当ては誤解を招くことを意味します。後者は前者を上書きするからです。つまり、2つは等しいと考えることができるというMartinの答えに同意します。NameAttributeが実際にそれらの間で異なる場合、私はそれらを等しいとは思わないでしょう。
Errorsatz

2
「私は慣用的なc#を理解しているので、==演算子は参照の等価性に使用され、Equals()メソッドは値の等価性に使用されます。」でも?私が見たほとんどの場合、==オーバーロードは値の平等のためです。最も重要な例はSystem.Stringです。
アルトゥーロトーレスサンチェス

回答:


17

ラップへの参照XElementは不変であるXmlWrapperため、同じ要素をラップする2つのインスタンス間に外部から観察可能な違いはないため、==この事実を反映するためにオーバーロードするのが理にかなっています。

クライアントコードは、ほぼ常に論理的な等価性を考慮します(デフォルトでは、参照型の参照等価性を使用して実装されます)。ヒープに2つのインスタンスがあるという事実は、クライアントが気にするべきではない実装の詳細です(実際に使用するインスタンスはObject.ReferenceEquals直接使用します)。


9

もっとも理にかなっていると思うなら

質問と回答は開発者の期待の問題であり、これは技術的な要件ではありません。

IFあなたはアイデンティティを持っており、それはその内容によって純粋に定義されていないにラッパーを検討し、その後、あなたの質問への答えはイエスです。

しかし、これは繰り返し発生する問題です。2つのラッパーが異なるオブジェクトをラップしているが、両方のオブジェクトの内容がまったく同じである場合、2つのラッパーは同等である必要がありますか?

答えは繰り返されます。IFコンテンツオブジェクトは何の個人的なアイデンティティを持たず、代わりに、純粋にその内容によって定義され、その後、コンテンツオブジェクトが効果的平等を示すであろうラッパーです。その後、コンテンツオブジェクトを別のラッパーでラップすると、その(追加の)ラッパーも同等になるはずです。

それはずっと亀です。


一般的なヒント

デフォルトの動作から逸脱する場合は、明示的に文書化する必要があります。開発者として、2つの参照タイプは、それらの内容が等しくても平等を示さないと予想しています。この動作を変更する場合は、すべての開発者がこの非定型的な動作を認識できるように、明確に文書化することをお勧めします。


慣用的なc#を理解しているように、==演算子は参照の平等のためであり、Equals()メソッドは値の平等のためです。

これがデフォルトの動作ですが、これは不動のルールではありません。それは慣習の問題ですが、正当な場合は慣習を変更できます。

stringここでの優れた例==は、値の等価性チェックでもあります(文字列インターンがない場合でも!)。どうして?簡単に言えば、文字列が値オブジェクトのように振る舞うことは、ほとんどの開発者にとってより直感的に感じるためです。

コードベース(または開発者の生活)がラッパー全体で値の平等性を示すことで大幅に簡素化できる場合は、それを実行します(ただし、文書化します)。

参照の等価性チェックをまったく必要としない(またはビジネスドメインによって役に立たない)場合、参照の等価性チェックを維持する意味はありません。開発者のエラー防ぐために、値の等価性チェックに置き換えてください
ただし、後で参照の等価性チェックが必要になった場合は、再実装にかなりの労力がかかる可能性があることを理解してください。


参照型がコンテンツの平等を定義しないと期待する理由を知りたいです。ほとんどのタイプは、参照の等価性が必要なためではなく、ドメインに必須ではないという理由だけで、等価性を定義しません。
カサブランカ

3
@casablanca:「期待」の異なる定義(要件と仮定)を解釈していると思います。ドキュメントがない場合==、これがデフォルトの動作であるため、参照の等価性をチェックすることを期待しています(つまり、想定しています)。ただし、==実際に値の等価性をチェックする場合、これが明示的に文書化されていることを期待しています(つまり、必要です)。デフォルトI'm curious why you expect that reference types won't define content equality.は定義さていませんが、それができないというわけではありません。私はそれを行うことができない(またはすべきではない)とは決して言いませんでした。
フラット

私はあなたが何を意味するのかわかります、明確にしてくれてありがとう
カサブランカ

2

基本的に文字列を比較しているので、同じXMLコンテンツを含む2つのラッパーが等しいと見なされない場合は、Equalsまたは==を使用してチェックしても驚かれます。

イディオムの規則は一般に参照型オブジェクトにとって意味がありますが、文字列はイディオムの意味で特別であり、技術的には参照型ですが、値として扱い、考慮する必要があります。

Wrapperの接尾辞は混乱を招きます。基本的に「XML要素ではありません」と表示されます。結局、参照型として扱う必要がありますか?意味的にはこれは意味がありません。クラスの名前がXmlContentであれば、それほど混乱しません。これは、技術的な実装の詳細ではなく、コンテンツに関心があることを示します。


「文字列から構築されたオブジェクト」と「基本的に文字列」の間にどこに制限を置きますか?外部のAPIの観点からは、かなり曖昧な定義のようです。APIのユーザーは実際に動作を推測するために内部を推測する必要がありますか?
アーサー・ハヴリチェク

クラス/オブジェクトの@Arthur Semanticsが手がかりを提供するはずです。そして、時にはそれはそれほど明白ではないので、この質問。実際の値型については、誰にとっても明らかです。実際の文字列にも。タイプは、比較にコンテンツIDまたはオブジェクトIDを含める必要があるかどうかを最終的に通知する必要があります。
マーティンマート
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.