Search code examples
c#expressionequalsexpression-treesoverload-resolution

Expression.Equal solution that honors "Equals" overloads and implicit operators


When compiling code at runtime using expression trees, we may find ourselves needing to check objects of indeterminate types for equality.

If we were to simply code this up by hand for each case, the compiler would take into account many things for us. The most ones that come to mind are:

  1. If a generic overload T1.Equals<T2> or T2.Equals<T1> is available, that is used.
  2. Otherwise, if either type has an implicit operator that would let us apply (1), that is used.
  3. Otherwise, bool Equals(object) is used (taking into account potential overrides, of course).

Regrettably, Expression.Equal(Expression left, Expression right) does not do all these things for us.

How can we achieve the same behavior that the compiler normally provides?


Solution

  • Assuming that we are only comparing objects of identical compile-time types, we can follow a simple two-step process when building the call expression:

    1. If the compile-time type implements IEquatable<T>, where T is the type itself, then call the corresponding Equals(T) method.

    This prevents boxing on structs that implement IEquatable<T>, which they often do for precisely that reason. It has the added benefit of calling the most obvious Equals overload on classes.

    1. Otherwise, call the regular (possibly overridden) Equals(object) method.

    There are two caveats.

    The first caveat is that Equals(T) methods in the absence of the IEquatable<T> interface are ignored. This may be considered reasonable: if the developer did not add the interface, it is not entirely clear whether they wanted the method to be used for this purpose or not.

    The second caveat is that, for classes, an IEquatable<T> interface on a base class is ignored, whereas the compiler might have preferred an overload corresponding to that. Luckily, we may reasonably expect Equals(object) to return the same result as Equals(T) for a T object. If not, the developer has provided an ambiguous definition of equality, and all bets are off.