Я реализовал класс NonEmptyString, который не позволяет создавать, если он не пуст. Я реализовал этот класс IEquatable<NonEmptyString> и IEquatable<string>. У меня есть переопределения для Equals(object obj), Equals(NonEmptyString other), Equals(string other) и GetHashCode(). Затем я написал несколько тестов и увидел, что практически все работает. За исключением 1 случая, когда статический метод Equals вызывается со строковым параметром, являющимся первым параметром. Смотрите эту строку здесь.
string text = "ASDF123";
NonEmptyString nonEmptyString = NonEmptyString.CreateUnsafe("ASDF123");
Assert.True(text == nonEmptyString);
Assert.True(nonEmptyString == text);
Assert.True(text.Equals(nonEmptyString)); // This one returns true as expected.
Assert.True(nonEmptyString.Equals(text));
Assert.True(Equals(text, nonEmptyString)); //This is the only one that doesn't work.
Assert.True(Equals(nonEmptyString, text));
Мне интересно, почему это должно быть так - когда я смотрю на реализацию метода Equals для объекта, он вызывает виртуальный метод Equals(object obj). Так что, если этот метод возвращает false, то я ожидаю, что то же самое должно произойти только для text.Equals(nonEmptyString), но это работает. Это реализация статического Equals, которую я вижу, когда вхожу в вызов.
public static bool Equals(object? objA, object? objB)
{
if (objA == objB)
{
return true;
}
if (objA == null || objB == null)
{
return false;
}
return objA.Equals(objB);
}
Я даже пытался таким образом переопределить операторы == для сравнения строки с NonEmptyString (на самом деле я не ожидал, что это поможет, но попробовать стоило)
public static bool operator ==(string obj1, NonEmptyString obj2)
public static bool operator !=(string obj1, NonEmptyString obj2)
public static bool operator ==(NonEmptyString obj1, string obj2)
public static bool operator !=(NonEmptyString obj1, string obj2)
Могу ли я что-нибудь сделать, чтобы это сработало? Ожидается ли, что это не должно работать? Это ошибка в .NET?
Вот основная реализация (я удалил из нее второстепенные части).
public sealed class NonEmptyString : IEquatable<string>, IEquatable<NonEmptyString>
{
private NonEmptyString(string value)
{
Value = value;
}
public string Value { get; }
public static NonEmptyString CreateUnsafe(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("You cannot create NonEmptyString from whitespace, empty string or null.");
}
return new NonEmptyString(value);
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
public override bool Equals(object obj)
{
return ReferenceEquals(this, obj) ||
obj is NonEmptyString otherNonEmpty && Equals(otherNonEmpty) ||
obj is string otherString && Equals(otherString);
}
public bool Equals(string other)
{
return Value.Equals(other);
}
public bool Equals(NonEmptyString other)
{
return Value.Equals(other?.Value);
}
public override string ToString()
{
return Value;
}
}
Не показывайте код в виде изображения. Мы не можем запускать изображения. Пожалуйста, опубликуйте полный минимальный воспроизводимый пример, чтобы мы могли запустить ваш код.
Спасибо, ребята, я обновил вопрос.





Я думаю, это потому, что Equals(object1, object2) это то же самое, что и вызов object1.ReferenceEquals(object2), а ReferenceEquals() нельзя переопределить. То есть: он пытается использовать метод stringReferenceEquals по умолчанию для сравнения вашего объекта класса со строкой, а не методы сравнения, которые вы определили для доступа к его свойству Value.
Я тоже думал об этом, но когда я вхожу в вызов статического Equals, он в конечном итоге вызывает objA.Equals(objB) - теперь я расширил вопрос с этим.
Интерфейс IEquatable в C# позволяет сравнивать два объекта одного типа на предмет равенства. Обычно он используется для переопределения метода Equals для пользовательского класса.
Однако IEquatable не работает со статическим Equals методом. Причина этого в том, что IEquatable требует реализации метода Equals на уровне экземпляра, то есть он сравнивает текущий объект с другим объектом того же типа.
Статический метод Equals, с другой стороны, не привязан к конкретному экземпляру класса и может сравнивать объекты разных типов. Обычно он используется в более общем смысле для проверки равенства между двумя объектами.
Чтобы IEquatable работал правильно, вам необходимо реализовать метод Equals на уровне экземпляра в классе, реализующем интерфейс. Например:
public class MyClass : IEquatable<MyClass>
{
public string Property { get; set; }
public bool Equals(MyClass other)
{
if (other == null)
return false;
return Property == other.Property;
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is MyClass))
return false;
return Equals((MyClass)obj);
}
public override int GetHashCode()
{
return Property.GetHashCode();
}
}
Затем вы можете использовать интерфейс IEquatable для сравнения экземпляров MyClass с помощью метода Equals:
MyClass obj1 = new MyClass { Property = "Test" };
MyClass obj2 = new MyClass { Property = "Test" };
bool areEqual = obj1.Equals(obj2); // true
Обратите внимание, что в приведенном выше примере статический метод Equals не участвует в сравнении на равенство.
Это не отвечает на вопрос.
Проблема, с которой вы столкнулись, заключается в том, что вы вызываете Equals перегрузку из классов string или object.
Посмотрите на этот код:
string text = "ASDF123";
NonEmptyString nonEmptyString = NonEmptyString.CreateUnsafe(text);
/* 3 */ Assert.True(text.Equals(nonEmptyString));
/* 5 */ Assert.True(Equals(text, nonEmptyString));
В строке 3 вызов Equals находится в экземпляре string, который понятия не имеет о вашем классе NonEmptyString, поэтому он всегда будет возвращать false независимо от того, равно ли базовое значение NonEmptyString.
В строке 5 вызов Equals находится в экземпляре object, который, опять же, не имеет представления о вашем классе NonEmptyString, поэтому он всегда будет возвращать false независимо от того, равно ли базовое значение NonEmptyString.
Вот оптимизированная компилятором версия вашего кода:
NonEmptyString nonEmptyString = NonEmptyString.CreateUnsafe("ASDF123");
Assert.True("ASDF123".Equals(nonEmptyString));
Assert.True(object.Equals("ASDF123", nonEmptyString));
Вы не контролируете эти Equals перегрузки.
Чтобы сделать вашу жизнь как можно проще, вы должны реализовать == и операторы явного и неявного приведения следующим образом:
public static bool operator ==(string obj1, NonEmptyString obj2) => obj2.Equals(obj1);
public static bool operator !=(string obj1, NonEmptyString obj2) => !obj2.Equals(obj1);
public static bool operator ==(NonEmptyString obj1, string obj2) => obj1.Equals(obj2);
public static bool operator !=(NonEmptyString obj1, string obj2) => !obj1.Equals(obj2);
public static implicit operator string(NonEmptyString nes) => nes.Value;
public static explicit operator NonEmptyString(string text) => NonEmptyString.CreateUnsafe(text);
Проблема в том, что на самом деле работает тот, что в строке 3. text.Equals(NonEmptyString) возвращает true. Вот почему я очень смущен, почему тот, что в строке 5, не возвращает true. Потому что это должен быть просто прокси-метод.
@PavelKalandra - Строка 3 у меня не работала, пока я не реализовал оператор неявного преобразования. Вы определились?
@PavelKalandra - ни один из них не является прокси-методом. string.Equals просто сравнивает string с SpanHelpers.SequenceEqual(ref Unsafe.As<char, byte>(ref strA.GetRawStringData()), ref Unsafe.As<char, byte>(ref strB.GetRawStringData()), (UIntPtr)(uint)(strA.Length * 2)), а object.Equals в конечном итоге использует ==, который сравнивает только ссылки для объектов. Попробуйте string x = "a"; bool result = (((object)"ab") == ((object)(x + "b")));, так как это возвращается False.
О, оснастка, да, у меня есть неявное преобразование. Таким образом, он просто вызывает Equality для строк, а не для объектов. Спасибо. Я принимаю этот ответ. Хотя я должен сказать, что с точки зрения пользователя я ожидаю, что IEquatable изменит поведение статического метода Equals, и хотя теперь я понимаю, почему он не работает, он все еще кажется мне ошибкой. Если бы такое поведение произошло в стороннем nuget, я бы обязательно пошел и попытался это исправить.
Вы должны предоставить (соответствующий) код для класса. Вы также должны предоставить тестовый код, который не работает, как код, а не просто как ссылку.