覆盖Equals方法并与字符串比较

4

我定义了一个C#类,其中包含一个字符串成员。就所有目的而言,将这个类视为字符串的子类(除非不允许)。我使用它来表示与特定格式匹配的强类型字符串字段(我已经大大简化了这个过程)。

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

现在,我希望能够直接将这个类与任何其他字符串(对象或字面量)进行比较。因此,我在类中实现了以下内容:

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

现在当我编写单元测试时,根据我传递给Assert.AreEqual的参数顺序不同,会得到不同的结果:

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

我猜这是因为在第一个断言中,它调用了 field.Equals(),而在第二个断言中则是调用 String.Equals()。很明显,我从错误的角度入手了。有人能给我一些见解吗?
另外一件事情是,我不能在这里使用结构体(值类型),因为在我的实际情况中,我是在一个基类中定义所有这些内容并从中继承的。
6个回答

9

基本上你不能做你想要的事情 - 你无法让string识别你的类以进行相等比较。你永远无法使它成为反射的 - 你永远无法使它遵守object.Equals的契约。

我个人会尝试重新设计它,使验证不是类型本身的一部分 - 而是业务实体(或其他实体)相关属性的一部分。


这样的类型使用起来很棘手,让其他人保持一致性也很棘手。最好将验证和其他业务逻辑保留在实体本身中。 - Andrew Hare
不幸的是,我正在将该类的实例用作字典和集合等中的键。因此,我需要Equals()和GetHashCode()的行为像值类型一样,以便它们在这些情况下能够正确工作。 - Scott Whitlock
重写Equals()方法,您可能需要考虑实现IComparable接口。http://msdn.microsoft.com/en-us/library/system.icomparable.aspx 我真希望微软能够将运算符重载从C#中移除。 - Chad Grant
@Deviant - 谢谢。实际上,如果我可以从String继承并添加自己的构造函数,我就不会有这个问题了。 - Scott Whitlock
@Scott:是的,你仍然会遇到相同的问题。"foo".Equals(instanceOfYourType) 仍然不会返回 true。相等和继承是一个根本性棘手的问题。 - Jon Skeet
@Jon - 你说得对,我尝试的方法行不通。(所以我才发了这个问题)。 我原来想问的问题在原帖底部:“显然我从错误的角度来解决这个问题。有没有人能给我一些见解?” 在现实世界中,我可以接受这两个断言语句都通过或都失败,但不能有一个通过一个失败。 因此,“foo”.Equals(instanceOfField)返回false是可以的,只要instanceOfField.Equals("foo")也返回false即可。这就是为什么我发布了我的答案的原因。 - Scott Whitlock

5
这在Effective Java中详细描述为第8项:重写equals时遵守通用约定。

equals方法实现了等价关系。

它是自反的,对称的,传递的,一致的,并且对于任何非空引用x,x.equals(null)必须返回false。打破对称性的示例类似于您的示例。 field类知道string类,但内置的string类不知道field。这是一种单向互操作性,应该被删除。

4

我建议不要将您的字段类隐式地用作字符串,而是强制使用以下类型:

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

0

根据大家的反馈和我的需求,这是我提出的可能解决方案(我将修改Equals方法如下):

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

这似乎允许在依赖于Equals和GetHashCode方法来确定值是否已经存在的字典和集合类中正确使用它。

此外,现在这两个都失败了:

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

这两个都通过:

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

这两者都通过:

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

这基本上是默认的Equals运算符,因此不覆盖它将给出等效的功能。由于您的字段表示文本数据,因此可以使用“return this.text == f.text”代替。但在这种情况下,您需要重写GetHashCode为类似于“return this.text.GetHashCode()”的内容,以确保集合类表现良好。 - Mikko Rantanen
是的,如果您查看原始问题,它有一个 GetHashCode() 的覆盖。 - Scott Whitlock
无法工作...规范的 Equals 实现有一项检查,例如 if this.GetType() != arg.GetType() 则返回 false。所以使用 "value".Equals( fieldObj ) 注定会失败。这是无法避免的。 - Gishu
@Gishu - 是的,这是有意为之的。只要"value".Equals(fieldObj)和fieldObj.Equals("value")返回相同的结果,那就没有问题。然后我可以回退到"value".Equals(fieldObj.ToString())。没有问题。 - Scott Whitlock

0

这是 String#Equals

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

提供非字符串参数给String#Equals方法将返回false。我建议重新考虑以解决这个问题。

顺便提一下,为什么会出现 (this != null) - 在实例方法中,这不是保证非空吗? - Gishu

-1

如果您在内部尝试验证 x 或 y 是否为 null,我建议使用 object.ReferenceEquals()

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接