web-dev-qa-db-ja.com

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

開発者が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は同じです」を出力する必要がありますか?または、それは奇妙なことです、つまり、 最小驚きのプリンシパル に違反していますか?

18
John Wu

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

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

16
casablanca

あなたがそれが最も理にかなっていると思うなら

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

[〜#〜] if [〜#〜]ラッパーにIDがないと見なし、その内容によってラッパーを純粋に定義すると、質問に対する答えは「はい」になります。 。

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

答えは繰り返されます。 [〜#〜] if [〜#〜]コンテンツオブジェクトは個人の識別情報を持たず、代わりにそれらのコンテンツによって純粋に定義され、コンテンツオブジェクトは実質的に同等性を示すラッパーです。次に、コンテンツオブジェクトを別のラッパーでラップすると、その(追加の)ラッパーも同等になります。

カメがずっと下にいる です。


一般的なヒント

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


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

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

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

コードベース(または開発者の生活)がラッパー全体で同等の値を示すようにすることで著しく簡略化できる場合は、それを検討してください(ただしドキュメント化してください )。

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

9
Flater

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

慣用的なルールは、参照タイプのオブジェクトに対して一般的に意味がありますが、文字列は慣用的な意味で特別であり、技術的には参照タイプですが、それらを値として扱い、見なすことになっています。

Wrapper postfixは混乱を引き起こします。それは基本的に「XML要素ではない」と言います。結局のところ、それを参照型として扱う必要がありますか?意味的にこれは意味がありません。クラスの名前がXmlContentである場合、混乱する可能性は低くなります。これは、技術的な実装の詳細ではなく、コンテンツに関心があることを示します。

2
Martin Maat