Reputation: 8038
I ran into some very wierd behaviour today whilst refactoring some code.
I had some code that looked something like this:
private AType Blah
{
get
{
return (from something in AList
where _x == null || something.x == _x
where _y == null || something.y == _y
where _z == null || something.z.IsSameAs(_z)
select something).Single();
}
}
I've anonomised the type and variable names as they aren't important to the question.
The type of _x and something.x was string and the _y and something.y was a reference type. Likewise _z and something.z were a reference type with a value comparison.
I thought I could do something like this:
public AType Blah
{
get { return AList.Single(something => DetailsMatch(something.x, something.y, something.z)); }
}
private bool DetailsMatch(string x, AnotherType y, AFurtherType z)
{
return NullOrCheck(_x, x) &&
NullOrCheck(_y, y) &&
NullOrCheck(_z, z.IsSameAs);
}
private bool NullOrCheck<T>(T value, T expected) where T : class
{
return NullOrCheck(value, v => v == expected);
}
private static bool NullOrCheck<T>(T value, Func<T,bool> check) where T : class
{
return value == null || check(value);
}
This all seemed to make sense but to my surprise some tests started failing. It turned out that the identical strings (the example was "1A04" and "1A04") were no longer being considered equal using the == operator.
Having looked at the following Can't operator == be applied to generic types in C#? it seems likely that the strings were being compared on reference equality instead of in the normal manner.
Is there a safe way to do this in c# or should using == in a generic method be considered dangerous for the above reason?
Just to confirm that this was the problem my fix consisted of inlining the offending methods in the string case resulting in:
private bool DetailsMatch(string x, AnotherType y, AFurtherType z)
{
return (_x == null || _x == x) &&
NullOrCheck(_y, y) &&
NullOrCheck(_z, z.IsSameAs);
}
Hey presto - everything works and the tests pass again
Upvotes: 1
Views: 178
Reputation: 144136
You can use Object.Equals
:
return NullOrCheck(value, v => object.Equals(v, expected));
The string
class overloads the static ==
operator to compare its two string arguments for equality i.e.
string first = "abc";
string second = "abc";
bool eq = first == second;
The call to ==
will use the overloaded ==
for strings, since the static type of first
and second
are both string
.
However, in
object first = "abc";
object second = "abc";
bool eq = first == second;
The ==
operator used will be the one defined by object
since the static type of first
and second
is object. Note that in this case, due to string interning, first
and second
will actually contain a reference to the same string, however this is not the case in general.
In a generic method, ==
will resolve to the static ==
defined for object
instead of the more specific version defined for string
. Since ==
is a simple reference equality check for object
s, this behaves differently.
The Equals
method is virtual and can be overridden to specialise equality checks for custom types. Therefore in
object first = "abc";
object second = "abc";
bool eq = first.Equals(second);
The string.Equals
method will be called, which will check the strings have the same values, instead of just the same reference.
The static object.Equals
method uses the virtual Equals
instance method, so it will also check the strings have the same value instead of just pointing to the same string instance. The static object.Equals
also checks its argument for null, so is safer than calling objA.Equals(objB)
directly.
Upvotes: 4