包装器在包装同一对象时是否应该使用==运算符比较相等?


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我一直推崇的所有时间里,我从未推崇==(但从未相反)。懒惰成语吗?如果我在没有显式强制转换的情况下得到不同的行为,则违反了最少的惊讶。
Radarbob

答案取决于NameAttribute的作用-修改基础元素?是额外的数据吗?示例代码的含义(以及是否应被视为相等)的变化取决于这一点,所以我认为你需要填写入。
Errorsatz

我向@Errorsatz表示歉意,但我想使示例简洁明了,并且我认为很明显,它将修改包装的元素(特别是通过修改名为“ name”的XML属性)。包装器允许对包装的元素进行读/写访问,但不包含其自身的状态。
吴John

4
好吧,在这种情况下,它们很重要-这意味着“ Hello”和“ World”分配具有误导性,因为后者将覆盖前者。这意味着我同意马丁的回答,即可以认为两者相等。如果NameAttribute实际上彼此不同,我不会认为它们相等。
Errorsatz

2
“据我了解惯用的c#,==运算符用于引用相等,而Equals()方法用于值相等。” 是吗 我见过的大多数时候,==重载都是为了值相等。最重要的示例是System.String。
Arturo TorresSánchez

Answers:


17

由于对包装对象的引用XElement是不可变的,因此在XmlWrapper包装相同元素的两个实例之间没有外部可观察到的差异,因此有必要进行重载==以反映这一事实。

客户端代码几乎总是关心逻辑相等性(默认情况下,它对引用类型使用引用相等性来实现)。堆上有两个实例的事实是客户端不需要关心的实现细节(那些实例将Object.ReferenceEquals直接使用)。


9

如果您认为这最有意义

问题与开发人员的期望有关,这不是技术要求。

如果您认为包装器没有身份,并且仅通过其内容定义身份,那么您的问题的答案是肯定的。

但这是一个反复出现的问题。当两个包装器包装不同的对象但两个对象的内容完全相同时,它们应该表现出相等性吗?

答案会重复。如果内容对象没有个人身份,而是仅由其内容定义,则内容对象是有效的包装程序,将表现出相等性。如果随后将内容对象包装在另一个包装器中,则该(附加)包装器也应显示相等性。

它的海龟一路下滑


一般提示

每当您偏离默认行为时,都应该对其进行明确记录。作为一名开发人员,我希望两个引用类型即使它们的内容相等也不会显示相等性。如果您更改了该行为,我建议您清楚地记录下来,以便所有开发人员都知道这种非典型行为。


据我了解惯用的C#,==运算符用于引用相等,而Equals()方法用于值相等。

这是它的默认行为,但这不是一个固定的规则。这是约定俗成的问题,但是可以在合理的情况下更改约定。

string是一个很好的例子,==值相等检查也是如此(即使没有字符串内部!)。为什么?简而言之:因为字符串的行为就像值对象,对大多数开发人员来说更直观。

如果可以通过使包装程序全面展现价值平等来显着简化您的代码库(或开发人员的生活),请继续努力(但要记录下来)。

如果您从不要求引用相等性检查(或者它们在您的业务领域中变得无用),那么就没有必要保留引用相等性检查。最好将其替换为值相等性检查,以防止开发人员出错
但是,请务必意识到,如果以后需要进行引用相等性检查,则重新实现它可能需要花费很多精力。


我很好奇为什么您期望引用类型不会定义内容相等。大多数类型不仅仅因为对它们的域不是必需的就定义了相等性,不是因为它们想要引用相等性。
卡萨布兰卡

3
@casablanca:我认为您在解释“期望”的另一种定义(即需求与假设)。没有文档,我期望(即假设)==检查引用是否相等,因为这是默认行为。但是,如果==实际检查值是否相等,我希望(即要求)明确记录下来。I'm curious why you expect that reference types won't define content equality.他们没有默认定义它,但这并不意味着它不能完成。我从来没有说过它不能(或不应该)完成,我只是不期望默认(即假设)它。
更加平坦

我明白您的意思,谢谢您的澄清。
卡萨布兰卡

2

您基本上是在比较字符串,所以如果两个包含相同XML内容的包装程序不相等,无论是使用Equals还是==检查,我都会感到惊讶。

通常,惯用规则对于引用类型对象可能有意义,但是字符串在惯用意义上是特殊的,尽管从技术上讲,它们是引用类型,但应该将其视为值。

您的包装后缀虽然增加了混乱。它基本上说“不是XML元素”。那我到底应该把它当作参考类型吗?从语义上讲这是没有意义的。如果将类命名为XmlContent,我就不会感到困惑。这表明我们在乎内容,而不是技术实施细节。


您将“从字符串构建的对象”和“基本上是字符串”之间的限制放在哪里?从外部API角度来看,这似乎是一个模糊的定义。API的用户实际上是否必须猜测内部才能猜测行为?
亚瑟·哈维利切克

类/对象的@Arthur语义学应该提供线索。有时并不是所有的事情都那么明显,因此出现了这个问题。对于实际值类型,任何人都将显而易见。对于真正的字符串也。该类型最终应告诉比较是应该包含内容还是对象标识。
Martin Maat
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.