JetBrains Rider 2023.2 Help

Code Inspection: Possible unintended reference comparison

C# compiler issues the 'Possible unintended reference comparison' warnings (CS0252/CS0253) in cases where you use equality operators (== or !=) on objects of types that inherit from one another and only one of the types overrides these operators.

The fact that the equality operators are overridden in one of the types means that value equality is expected when objects of this type are compared. Comparing these objects with objects of types where the equality operators are not overridden will lead to using the overload from System.Object, which checks reference equality. So a warning is issued because comparing object references is most likely erroneous in this situation.

Although JetBrains Rider knows about these warnings and provides design-time notifications for them, it goes one step further and detects another case of possible unintended reference comparison — when only one of the compared types overrides Equals(). MSDN documentation says: Any class that represents a value, essentially any value type, or a set of values as a group, such as a complex number class, should override Equals. So if Equals() is not overridden for the second object's type, then the comparison falls back to the implementation of Equals() in System.Object, which performs a reference identity check, which is most probably not desired for a value type.

Here is a code listing that illustrates both situations:

class MyType { } class TypeWithEquals : MyType { public override bool Equals(object obj) { throw new NotImplementedException(); } } class TypeWithEqOperators : MyType { public static bool operator ==(TypeWithEqOperators left, TypeWithEqOperators right) { throw new NotImplementedException(); } public static bool operator !=(TypeWithEqOperators left, TypeWithEqOperators right) { throw new NotImplementedException(); } } class Test { public Test(TypeWithEquals withEquals, TypeWithEqOperators withEqOperators, MyType parentObject) { bool a = withEqOperators == parentObject; // CS0253 bool b = withEquals == parentObject; // No compiler warnings, but JetBrains Rider issues its own warning here } }

Another case for this inspection is comparing interface types using equality operators (== or !=). As these operators are static, the compiler will default to System.Object.ReferenceEquals() — that is to the reference comparison — even if the equality operators are overridden to value comparison in the interface implementations.

JetBrains Rider looks for interface implementations that override equality operators and/or Equals(), and if it detects such an implementation it issues a warning:

interface ISomeInterface{ } class SomeImplementation : ISomeInterface { public override bool Equals(object obj) => throw new NotImplementedException(); public override int GetHashCode() => throw new NotImplementedException(); } void ATest(ISomeInterface x, ISomeInterface y) { bool a = x == y; //Possible unintended reference comparison }

Note that this inspection will look for interface implementations in the whole solution (including referenced libraries), whose locations are not always obvious. In the example below, JetBrains Rider will detect implementations of ICollection<object> that override Equals() in the mscorlib and issue a warning even if none of your own implementations of ICollection<object> override Equals() or equality operators.

void SomeTest(ICollection<object> x, ICollection<object> y) { bool a = x == y; //Possible unintended reference comparison }

Although this case may seem a bit defensive, your code might be more straightforward and probably safer if you use non-static methods for comparing interfaces. That is, you can use ReferenceEquals() if you mean to check reference equality, or Equals() if you expect it to be overridden in the implementations, or any specific method, for example Enumerable.SequenceEqual(), which checks whether collections contain equal objects in the same order.

Last modified: 10 August 2022