I implemented a class called NonEmptyString which doesn't allow creation when it's not empty. I made this class implement IEquatable<NonEmptyString>
and IEquatable<string>
. I have overrides for Equals(object obj)
, Equals(NonEmptyString other)
, Equals(string other)
, and GetHashCode()
. I then wrote some tests and saw that pretty much everything works. Except for 1 case when the static Equals method is called with the string parameter being the first parameter. See this line here.
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));
I'm wondering why it should be the case - when I look at the implementation of Equals method on object, it does call the virtual Equals(object obj)
method. So if that method returns false, then I'd expect that the same should happen for just text.Equals(nonEmptyString)
- but that one works. This is the implementation of the static Equals I see when I go into the call.
public static bool Equals(object? objA, object? objB)
{
if (objA == objB)
{
return true;
}
if (objA == null || objB == null)
{
return false;
}
return objA.Equals(objB);
}
I even tried overriding the ==
operators for comparing a string with a NonEmptyString in this manner (I didn't really expect that to help, but it was worth a try)
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)
Is there anything I can do to make this work? Is it expected that this should not work? Is it a bug in .NET?
Here's the core implementation (I removed the non-important parts from it.)
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;
}
}