Переопределение Equals и сравнение со строкой - PullRequest
4 голосов
/ 27 апреля 2009

Я определил класс 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 голосов
/ 27 апреля 2009

По сути, вы не можете делать то, что хотите - нет способа заставить string распознавать свой класс в целях равенства. Вы никогда не сможете сделать это рефлексивным - вы никогда не сможете заставить его подчиняться контракту object.Equals.

Я бы лично попытался изменить его так, чтобы у вас не было валидации как части самого типа - включить его в соответствующие свойства бизнес-объектов (или какими бы они ни были).

5 голосов
/ 27 апреля 2009

Это подробно описано в Effective Java как пункт 8: соблюдайте общий контракт при переопределении equals.

Метод equals реализует отношение эквивалентности.

Он является рефлексивным, симметричным, транзитивным, последовательным и для любой ненулевой ссылки x, x.equals(null) должен возвращать false. Приведенный пример нарушения симметрии аналогичен вашему.

Класс

field знает класс string, но встроенный класс string не знает field. Это односторонняя совместимость и должна быть удалена.

4 голосов
/ 27 апреля 2009

Я бы отговорил любого, кто неявно использует ваш класс поля как String, и принудительно использовал бы этот тип:

string valid = "Some String";
field target = new field(valid);
Assert.AreEqual(target.toString(), valid); 
Assert.AreEqual(valid, target.toString());
0 голосов
/ 18 марта 2013

Я предлагаю использовать object.ReferenceEquals () , если вы внутренне пытаетесь проверить, является ли x или y нулевым.

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());
    }
}
0 голосов
/ 27 апреля 2009

Это строка # равно

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

Предоставление аргумента, отличного от String, в String # Equals вернет false. Я бы предложил «переосмыслить», чтобы обойти это.

0 голосов
/ 27 апреля 2009

Исходя из отзывов каждого и моих собственных потребностей, вот что я предлагаю в качестве возможного решения (я изменяю метод 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
...