The default equality semantics for CLR reference types are to compare by object identity. Unless two references refer to the same object instance, they are considered distinct.
Especially in scenarios such as this, where set operations are desirable, we may need to customize this behavior, providing our objects with value semantics, not reference semantics.
There are multiple ways to achieve this. We can define it at the algorithm level, passing a custom comparison into the Except
method. Or we can define it at the type level, defining equality for all instances of the set element type in question. Which approach is best will depend partially on how the objects are used and if we can modify the source code for the class defining the element type of the sets.
In his answer, Gilad Green provides very helpful references to Microsoft documentation on the nature and use cases for these approaches.
In the following example, we use a custom comparer to define equality.
sealed class PointComparer: EqualityComparer<TBL_Points>
{
public override bool Equals(TBL_Points x, TBL_Points y) => x.Code == y.Code;
public override int GetHashCode(TBL_Points point) => point.Code.GetHashCode();
}
We now need to instantiate this comparer and pass it into Except
like so
var pointsToAdd = selectedPoints
.AsEnumerable()
.Except(plistPointsFromDB, new PointComparer())
.ToList();
Note that I cleaned up the naming (except for that of the type itself which is still awful) and removed the unnecessary use of explicit types.
The other approach is to define equality for the type TBL_Points
itself (again that needs renaming)
class TBL_Points: IEquatable<TBL_Points>
{
public bool Equals(TBL_Points other) => Code == other?.Code;
public sealed override bool Equals(object obj) => obj is TBL_Points o && Equals(o);
public sealed override int GetHashCode() => Code.GetHashCode();
public static bool operator ==(TBL_Points x, TBL_Points y) => x?.Equals(y) == true;
public static bool operator !=(TBL_Points x, TBL_Points y) => !(x == y);
}
The above defines equality semantics for all uses of the type. This is convenient, since we no longer need to create and pass instances of a comparison object into algorithms. This also ensures consistency across all uses.