IEquatable<string>不适用于静态 Equals 方法

IEquatable<string> doesn't work with static Equals method

提问人:Pavel Kalandra 提问时间:8/21/2023 最后编辑:Pavel Kalandra 更新时间:8/21/2023 访问量:94

问:

我实现了一个名为 NonEmptyString 的类,当它不为空时不允许创建。我让这个类实现了 和 .我有 、 、 和 的覆盖。然后我写了一些测试,发现几乎所有东西都有效。除了 1 种情况,即调用静态 Equals 方法时,字符串参数是第一个参数。请在此处查看此行。IEquatable<NonEmptyString>IEquatable<string>Equals(object obj)Equals(NonEmptyString other)Equals(string other)GetHashCode()

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 方法的实现时,它确实调用了虚拟方法。因此,如果该方法返回 false,那么我希望同样的事情应该发生 - 但那个有效。这是我进入调用时看到的静态 Equals 的实现。Equals(object obj)text.Equals(nonEmptyString)

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;
    }
}
C# .NET 字符串 相等性 IEquatable

评论

1赞 jmcilhinney 8/21/2023
您应该提供类的(相关)代码。您还应该将失败的测试代码作为代码提供,而不仅仅是链接。
1赞 Enigmativity 8/21/2023
不要将代码显示为图像。我们无法运行映像。请发布一个完整的最小可重现示例,以便我们可以运行您的代码。

答:

0赞 Peter Dongan 8/21/2023 #1

我认为这是因为与调用相同,不能被覆盖。即:它尝试使用默认方法将您的类对象与字符串进行比较,而不是您为访问其属性而定义的比较方法。Equals(object1, object2)object1.ReferenceEquals(object2)ReferenceEquals()stringReferenceEqualsValue

评论

0赞 Pavel Kalandra 8/21/2023
我也考虑过这一点,但是当我进入静态 Equals 的调用时,它最终会调用 - 我现在用这个扩展了这个问题。objA.Equals(objB)
-1赞 Ehsan Nozari 8/21/2023 #2

C# 中的 IEquatable 接口提供了一种比较相同类型的两个对象是否相等的方法。它通常用于重写自定义类的 Equals 方法。

但是,IEquatable 不适用于静态方法。这样做的原因是 IEquatable 需要以实例级方式实现 Equals 方法,这意味着它将当前对象与相同类型的另一个对象进行比较。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 接口通过 Equals 方法比较 MyClass 的实例:

MyClass obj1 = new MyClass { Property = "Test" };
MyClass obj2 = new MyClass { Property = "Test" };

bool areEqual = obj1.Equals(obj2);  // true

请注意,在上面的示例中,静态方法不参与相等性比较。Equals

评论

0赞 Enigmativity 8/21/2023
这并不能回答这个问题。
2赞 Enigmativity 8/21/2023 #3

您似乎遇到的问题是当您从 或 类调用重载时。Equalsstringobject

请看这段代码:

string text = "ASDF123";
NonEmptyString nonEmptyString = NonEmptyString.CreateUnsafe(text);
/* 3 */ Assert.True(text.Equals(nonEmptyString));
/* 5 */ Assert.True(Equals(text, nonEmptyString));

在第 3 行,对的调用是在不知道您的类的实例上 - 因此,无论 的基础值是否相等,它都会始终返回。EqualsstringNonEmptyStringfalseNonEmptyString

在第 5 行,对的调用是在实例上,该实例同样不知道您的类 - 因此,无论 的基础值是否相等,它都将始终返回。EqualsobjectNonEmptyStringfalseNonEmptyString

下面是代码的编译器优化版本:

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

评论

0赞 Pavel Kalandra 8/21/2023
问题是第 3 行上的那个实际上有效。 返回 true。这就是为什么我非常困惑为什么第 5 行上的那个没有返回 true。因为它应该只是一个代理方法。text.Equals(NonEmptyString)
0赞 Enigmativity 8/21/2023
@PavelKalandra - 在我实现隐式转换运算符之前,第 3 行对我来说不起作用。你定义了一个吗?
0赞 Enigmativity 8/21/2023
@PavelKalandra - “代理”方法也不是。 纯粹与 with 和最终使用进行比较,仅比较引用对于对象是相等的。尝试,因为这会返回 。string.EqualsstringSpanHelpers.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
1赞 Pavel Kalandra 8/21/2023
哦,啪,是的,我确实有一个隐式转换。所以它只是在字符串上调用 Equality 而不是在对象上调用 Equality 。谢谢。我接受这个答案。虽然我不得不说,从用户的角度来看,我希望 IEquatable 能够改变静态 Equals 方法的行为,即使我现在明白了为什么它不起作用,但对我来说它仍然感觉像一个错误。如果这种行为发生在第三方 nuget 中,我肯定会进去尝试修复它。