Why ICollection<>.Contains ignores my overridden Equals and the IEquatable<> interface? - c#

I have an issue with a navigation property in an entity framework project.
Here is the class MobileUser:
[DataContract]
[Table("MobileUser")]
public class MobileUser: IEquatable<MobileUser>
{
// constructors omitted....
/// <summary>
/// The primary-key of MobileUser.
/// This is not the VwdId which is stored in a separate column
/// </summary>
[DataMember, Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
[DataMember, Required, Index(IsUnique = true), MinLength(VwdIdMinLength), MaxLength(VwdIdMaxLength)]
public string VwdId { get; set; }
// other properties omitted ...
[DataMember]
public virtual ICollection<MobileDeviceInfo> DeviceInfos { get; private set; }
public bool Equals(MobileUser other)
{
return this.UserId == other?.UserId || this.VwdId == other?.VwdId;
}
public override bool Equals(object obj)
{
if(object.ReferenceEquals(this, obj))return true;
MobileUser other = obj as MobileUser;
if (other == null) return false;
return this.Equals(other);
}
public override int GetHashCode()
{
// ReSharper disable once NonReadonlyMemberInGetHashCode
return VwdId.GetHashCode();
}
public override string ToString()
{
return "foo"; // omitted actual implementation
}
#region constants
// irrelevant
#endregion
}
The relevant part is this navigation property:
public virtual ICollection<MobileDeviceInfo> DeviceInfos { get; private set; }
This is the class MobileDeviceInfo:
[DataContract]
[Table("MobileDeviceInfo")]
public class MobileDeviceInfo : IEquatable<MobileDeviceInfo>
{
[DataContract]
public enum MobilePlatform
{
[EnumMember]
// ReSharper disable once InconsistentNaming because correct spelling is iOS
iOS = 1,
[EnumMember] Android = 2,
[EnumMember] WindowsPhone = 3,
[EnumMember] Blackberry = 4
}
// constructors omitted ...
[DataMember, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int DeviceInfoId { get; private set; }
[DataMember, Required, Index(IsUnique = true), MinLength(DeviceTokenMinLength), MaxLength(DeviceTokenMaxLength)]
public string DeviceToken { get; set; }
[DataMember, Required, MinLength(DeviceNameMinLength), MaxLength(DeviceNameMaxLength)]
public string DeviceName { get; set; }
[DataMember, Required]
public MobilePlatform Platform { get; set; }
// other properties ...
[DataMember]
public virtual MobileUser MobileUser { get; private set; }
/// <summary>
/// The foreign-key to the MobileUser.
/// This is not the VwdId which is stored in MobileUser
/// </summary>
[DataMember, ForeignKey("MobileUser")]
public int UserId { get; set; }
public bool Equals(MobileDeviceInfo other)
{
if (other == null) return false;
return DeviceToken == other.DeviceToken;
}
public override string ToString()
{
return "Bah"; // implementation omitted
public override bool Equals(object obj)
{
if (ReferenceEquals(this, obj)) return true;
MobileDeviceInfo other = obj as MobileDeviceInfo;
if (other == null) return false;
return Equals(other);
}
public override int GetHashCode()
{
// ReSharper disable once NonReadonlyMemberInGetHashCode
return DeviceToken.GetHashCode();
}
#region constants
// irrelevant
#endregion
}
As you can see, it implements IEquatable<MobileDeviceInfo> and overrides also Equals and GetHashCode from System.Object.
I have following test, i've expected that Contains would call my Equals but it does not. It seems to use Object.ReferenceEquals instead, so won't find my device because it's a different reference:
var userRepo = new MobileUserRepository((ILog)null);
var deviceRepo = new MobileDeviceRepository((ILog)null);
IReadOnlyList<MobileUser> allUser = userRepo.GetAllMobileUsersWithDevices();
MobileUser user = allUser.First();
IReadOnlyList<MobileDeviceInfo> allDevices = deviceRepo.GetMobileDeviceInfos(user.VwdId, true);
MobileDeviceInfo device = allDevices.First();
bool contains = user.DeviceInfos.Contains(device);
bool anyEqual = user.DeviceInfos.Any(x => x.DeviceToken == device.DeviceToken);
Assert.IsTrue(contains); // no, it's false
The second approach with LINQ's Enumerable.Any returns the expected true.
If i don't use user.DeviceInfos.Contains(device) but user.DeviceInfos.ToList().Contains(device) it also works as expected since List<>.Contains uses my Equals.
The actual type of the ICollection<> seems to be a System.Collections.Generic.HashSet<MobileDeviceInfo> but if i use following code that uses also a HashSet<> it again works as expected:
bool contains = new HashSet<MobileDeviceInfo>(user.DeviceInfos).Contains(device); // true
So why are only references compared and my custom Equals is ignored?
Update:
even more confusing is the result is false even if i cast it to the
HashSet<MobileDeviceInfo>:
// still false
bool contains2 = ((HashSet<MobileDeviceInfo>)user.DeviceInfos).Contains(device);
// but this is true as already mentioned
bool contains3 = new HashSet<MobileDeviceInfo>(user.DeviceInfos).Contains(device);
Update 2:: the reason for this really seems to be that both HashSets use different comparers. The entity-framework-HashSet uses a:
System.Data.Entity.Infrastructure.ObjectReferenceEqualityComparer
and the standard HashSet<> uses a:
GenericEqualityComparer<T>
That explains the issue, although i don't understand why entity framework uses an implementation that ignores custom Equals implementations under certain circumstances. That's a nasty trap, isn't it?
Conclusion: never use Contains if you don't know what comparer will be used or use Enumerable.Contains with the overload that takes a custom comparer:
bool contains = user.DeviceInfos.Contains(device, EqualityComparer<MobileDeviceInfo>.Default); // true

From the EF source, you might stumble on CreateCollectionCreateDelegate, which seems to be called as part of hooking up navigation properties.
This calls EntityUtil.DetermineCollectionType and returns a HashSet<T> as the type if that is compatible with the property.
Then, armed with HashSet<T>, it makes a call to DelegateFactory.GetNewExpressionForCollectionType which, per the code and the description, handles HashSet<T> as a special case and passes it an ObjectReferenceEqualityComparer in the constructor.
So: the HashSet<T> EF creates for you isn't using your equality implementation, it uses reference equality instead.

Why ICollection<>.Contains ignores my overridden Equals and the IEquatable<> interface?
Because there is no requirement from the implementors of the interface to do so.
ICollection<T>.Contains method MSDN documentation states:
Determines whether the ICollection<T> contains a specific value.
And then
Remarks
Implementations can vary in how they determine equality of objects; for example, List<T> uses Comparer<T>.Default, whereas Dictionary<TKey, TValue> allows the user to specify the IComparer<T> implementation to use for comparing keys.
Side note: Looks like they messed up IComparer<T> with IEqualityComparer<T>, but you get the point :)
Conclusion: never use Contains if you don't know what comparer will be used or use Enumerable.Contains with the overload that takes a custom comparer
According to the Enumerable.Contains<T>(IEnumerable<T>, T) method overload (i.e. without custom comparer) documentation:
Determines whether a sequence contains a specified element by using the default equality comparer.
which sounds like your overrides will be called. But then comes the following:
Remarks
If the type of source implements ICollection<T>, the Contains method in that implementation is invoked to obtain the result. Otherwise, this method determines whether source contains the specified element.
which conflicts with the initial statement.
It's really a mess. All I can say is that I fully agree with that conclusion!

Related

Matching conditional template of a unique set of objects under ambiguous conditions

It's better to start with code
public interface ICondition<TObject> where TObject : notnull
{
public TObject? Value { get; } //Condition value for primitives
public bool InverseResult { get; init; }
public bool IsSatisfiedBy(TObject obj)
{
return InverseResult ? !Check(obj) : Check(obj);
}
public bool Check(TObject obj);
}
This interface is implemented much in classes like IntegerCondition, BooleanCondition. And other primitives. For example GreaterIntegerCondition, ExactlyIntegerCondition
public interface ITemplate<TObject> : ICondition<TObject> where TObject : notnull
{
TObject? ICondition<TObject>.Value => default;
public IEnumerable<IMappedCondition<TObject>> ConditionsMap { get; }
bool ICondition<TObject>.InverseResult
{
get => false;
init { }
}
public string TemplateName { get; set; }
bool ICondition<TObject>.Check(TObject obj)
{
return ConditionsMap.All(c => c.CheckCondition(obj)) && ConditionsMap.Any();
}
}
This interface extends previous. It can contain many of conditions that mapped to TObject's properties. It is used on more complex objects that can have many properties.
At the end I have this class (simplified version below)
public class FooBunchTemplate
{
public List<ICondition<Foo>> Conditions { get; }
public bool CheckBunch(HashSet<Foo> bunch)
{
var remainingConditions = Conditions.ToList();
foreach(Foo foo in bunch)
{
var matched = remainingConditions.FirstOrDefault(c => c.IsSatisfiedBy(foo));
if(matched == null) return false;
remainingConditions.Remove(matched);
}
return true;
}
}
So, we have set of Foo. Every Foo in HashSet is unique. But problem is the one Foo must match only one condition "slot". There are templates that can be satisfied by different objects (even hundreads of them)
If I just exclude matches, then there might be a situation where the more general condition will be excluded by Foo which would fit also the narrower pattern. Then the CheckBunch() will return false, although if the conditions are set correctly, it should have returned true.
You can think of it as a slot machine. The order is unimportant, but the link 1 condition - 1 object is important.
Example template:
A or B
A
C
Bunch:
CAB
We search C template and exclue 3rd
We search A template and exclude 1st
We search B template but 1st was excluded before.
False is returned
//Wrong result. CAB matches this template, because we can put A in second slot and B in first.
Which approach will be fast and reliable in that situation. Number of conditions in BunchTemplate always fit count of Foo in bunch

How to implement multiple GetHashCode methods?

I have an interface which defines a composite key:
public interface IKey : IEquatable<IKey>
{
public bool KeyPart1 { get; }
public uint KeyPart2 { get; }
int GetHashCode(); // never gets called
}
I have an object (with an ID) to which I want to add the composite key interface:
public class MyObject: IEquatable<MyObject>, IKey
{
public MyObject(int i, (bool keyPart1, uint keyPart2) key) {
{
Id=i;
KeyPart1 = key.keyPart1;
KeyPart2 = key.keyPart2;
}
public int Id { get; }
public bool KeyPart1 { get; }
public uint KeyPart2 { get; }
public bool Equals(MyObject other) => this.Id == other.Id;
public override bool Equals(object other) => other is MyObject o && Equals(o);
public override int GetHashCode() => Id.GetHashCode();
bool IEquatable<IKey>.Equals(IKey other) => this.KeyPart1 == other.KeyPart1
&& this.KeyPart2 == other.KeyPart2;
int IKey.GetHashCode() => (KeyPart1, KeyPart2).GetHashCode(); // never gets called
}
However, when have a list of these objects and try to group them using the interface, the grouping fails:
var one = new MyObject(1, (true, 1));
var two = new MyObject(2, (true, 1));
var three = new MyObject(1, (false, 0));
var items = new[] { one, two, three };
var byId = items.GroupBy(i => i);
// result: { [one, three] }, { [two] } -- as expected
var byKey = items.GroupBy<MyObject, IKey>(i => i as IKey);
// result: { [one, two, three] } // not grouped (by 'id' or 'key')
// expected: { [one, two] }, { [three] }
I'd expected that byId would have the items grouped by the Id property, and byKey would have the items grouped by the Key property.
However, byKey is not grouped at all. It appears that the override GetHashCode() method is always used rather than the explicitly implemented interface method.
Is it possible to implement something like this, where the type of the item being grouped determines the hash method to use (avoiding an EqualityComparer)?
I noticed this problem when passing the cast objects to another method expecting an IEnumerable<IKey>. I have a few different types implementing IKey and those with an existing GetHashCode() method did not work, while the others did.
Please note the objects have been simplified here and that I cannot easily change the interfaces (e.g. to use ValueTuple instead).
The GetHashCode() used in equality is either:
the one defined via object.GetHashCode(), if no equality comparer is provided
IEqualityComparer<T>.GetHashCode(T), if an equality comparer is provided
Adding your own GetHashCode() method on your own interface does nothing, and it will never be used, as it is not part of an API that the framework/library code knows about.
So, I'd forget about IKey.GetHashCode(), and either (or both):
make MyObject.GetHashCode() provide the functionality you need, or
provide a custom equality comparer separately to the MyObject instance
There are overloads of GroupBy that accept an IEqualityComparer<TKey>, for the second option.

Setting up a simple iequatable class c#

Cant find a simple answer. My problem is I am trying to compare the VALUE of an object in a list to the VALUE of an object...
my class:
public class MatchList
{
public int SomeInt { get; set; }
public decimal SomeDecimal { get; set; }
}
I create theMatchList. It seems that I can only compare the object and not the values for object with 'theMatchList.Contains...'
MatchList ML = new MatchList();
ML.SomeInt = 12;
ML.SomeDecimal = 2.3;
if (theMatchlist.Contains(ML))
{
DoSomething;
}
How do get to fire 'DoSomething'? Assuming that there is an entry in 'theMatchList' where the values equal 12 and 2.3 respectively. I know it has something to do with iequatable, but I dont quite understand how that works. Thanks in advance!
Your naming is a bit unclear, I assume that you actually have a List<MatchList> that you want to find a particular MatchList in (I suggest renaming MatchList to at least MatchItem in that case and preferable something more descriptive).
Then from the documentation of List<T>.Contains:
This method determines equality by using the default equality comparer, as defined by the object's implementation of the IEquatable<T>.Equals method for T (the type of values in the list).
So you will have to implement IEquatable<T> for your class. In addition, the advice is that
[i]f you implement Equals, you should also override the base class implementations of Object.Equals(Object) and GetHashCode so that their behavior is consistent with that of the IEquatable.Equals method.
If you implement GetHashCode, its result should not change over the lifetime of your object. In most cases, making the class immutable is sufficient. If you need to be able to update the fields, you need to implement GetHashCode differently.
So all in all, if you want to use Contains your class will end up looking something like below:
public class MatchList : IEquatable<MatchList>
{
// Note: Fields are readonly to satisfy GetHashCode contract
private readonly int someInt;
private readonly decimal someDecimal;
// Public constructor creates immutable object
public MatchList(int myInt, decimal myDecimal)
{
this.someInt = myInt;
this.myDecimal = myDecimal;
}
// Properties are now read-only too.
public int SomeInt { get { return this.someInt; } }
public decimal SomeDecimal { get { return this.someDecimal; } }
// Implementation of IEquatable<MatchList>
public bool Equals( MatchList other )
{
return (other != null)
&& (this.SomeInt == other.SomeInt)
&& (this.SomeDecimal == other.SomeDecimal);
}
// Override of Object.Equals
// Calls the IEquatable.Equals version if possible.
public override bool Equals( object obj )
{
return (obj is MatchList) && this.Equals(obj as MatchList);
}
public override int GetHashCode()
{
return (this.someInt * 17) ^ this.someDecimal.GetHashCode();
}
}
As I commented, your question is pretty unclear so I'll do my best to explain the concept.
It's pretty likely what you were trying to code is the items in the list not the list itself:
public class MatchItem : IEquatable<MatchItem>
{
public int SomeInt { get; set; }
public decimal SomeDecimal {get; set; }
public bool Equals(MatchItem item)
{
if(item == null)
return false;
return this.SomeInt == item.SomeInt && this.SomeDecimal == item.SomeDecimal;
}
// You should also override object.ToString, object.Equals & object.GetHashCode.
// Omitted for brevity here!
}
You'll note that has an implementation of IEquatable<MatchItem> which allows it to be compared to other instances of MatchItem.
Thereafter, this code will work:
var items = new List<MatchItem>()
{
new MatchItem{SomeInt = 1, SomeDecimal = 0.3M},
new MatchItem{SomeInt = 12, SomeDecimal = 2.3M}
};
var searchItem = new MatchItem{SomeInt = 1, SomeDecimal = 0.3M};
Console.WriteLine(items.Contains(searchItem)); // true
Working example: http://rextester.com/ZWNC6890

Removing duplicates from a list<int,int> [duplicate]

I have to distinct list of object but NOT only by ID because sometimes two different objects have same ID.
I have class:
public class MessageDTO
{
public MessageDTO(MessageDTO a)
{
this.MsgID = a.MsgID;
this.Subject = a.Subject;
this.MessageText = a.MessageText;
this.ViewedDate = a.ViewedDate;
this.CreatedDate = a.CreatedDate;
}
public int? MsgID { get; set; }
public string Subject { get; set; }
public string MessageText { get; set; }
public System.DateTime? ViewedDate { get; set; }
public System.DateTime? CreatedDate { get; set; }
}
How I can distinct list of:
List<MessageDTO> example;
Thanks
Use LINQ.
public class MessageDTOEqualityComparer : EqualityComparer<MessageDTO>
{
public bool Equals(MessageDTO a, MessageDTO b)
{
// your logic, which checks each messages properties for whatever
// grounds you need to deem them "equal." In your case, it sounds like
// this will just be a matter of iterating through each property with an
// if-not-equal-return-false block, then returning true at the end
}
public int GetHashCode(MessageDTO message)
{
// your logic, I'd probably just return the message ID if you can,
// assuming that doesn't overlap too much and that it does
// have to be equal on the two
}
}
Then
return nonDistinct.Distinct(new MessageDTOEqualityComparer());
You can also avoid the need for an extra class by overriding object.Equals(object) and object.GetHashCode() and calling the empty overload of nonDistinct.Distinct(). Make sure you recognize the implications of this decision, though: for instance, those will then become the equality-testing functions in all non-explicit scopes of their use. This might be perfect and exactly what you need, or it could lead to some unexpected consequences. Just make sure you know what you're getting into.
I you want to use other properties, you should implement IEqualityComparer interface. More on: msdn
class MsgComparer : IEqualityComparer<MessageDTO>
{
public bool Equals(MessageDTO x, MessageDTO Oy)
{
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public int GetHashCode(MessageDTO m)
{
//it must br overwritten also
}
}
Then:
example.Distinct(new MsgComparer());
You could also overwrite Equals in MessageDTO class:
class MessageDTO
{
// rest of members
public override bool Equals(object obj)
{
// your stuff. See: http://msdn.microsoft.com/en-us/library/ms173147%28v=vs.80%29.aspx
}
public override int GetHashCode()
{
}
}
Then it's enough:
example.Distinct();
You could use the extension method DistinctBy from the MoreLinq library:
string[] source = { "first", "second", "third", "fourth", "fifth" };
var distinct = source.DistinctBy(word => word.Length);
See here:
I recommend you using solution of #Matthew Haugen
In case you don't want to create a new class for that, there is a way to use LINQ by grouping you list by distinct field(s) then select the first item on this group. For example:
example.(e => new { e.MsgID, e.Subject }).Select(grp => grp.FirstOrDefault());

JSON.NET Serialization - How does DefaultReferenceResolver compare equality?

I am using JSON.NET 6.0.3. I have changed PreserveReferences option as follows:
HttpConfiguration.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
My object graph resembles the following:
public class CarFromManufacturer
{
public int CarID { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public CarManufacturer Manufacturer { get; set; }
}
public class CarManufacturer
{
public int ManufacturerID { get; set; }
public string Name { get; set; }
}
My WebAPI controller is returning the result set of IEnumerable[CarFromManufacturer]. So the result could be a list of 5 cars from two unique manufacturer objects. I am expecting the JSON result to list each manufacturer only once fully serialized and then subsequent uses of the same Manufacturer to be $ref ID to the original's $id. That is not happening.
Even though I can't find a single piece of documentation that speaks about how equality is established for the ReferenceResolver, I've implemented IEquatable<CarManufacturer> along with override of base.Equals and base.GetHashCode() with no luck.
I'd like to avoid implementing my own IReferenceResolver because have very similar object graphs working as expected in the same project.
The only thing I can think of is that I am using factory objects and instead of creating each unique CarManufacturer first, then creating the instances of CarFromManufacturer passing in CarManufacturer... i am creating a new instance of the CarManufacturer. This would explain why the objects aren't equal, but that's why I implemented IEquatable and overrides of base.Equals(object) and base.GetHashCode().
I've looked into the source for DefaultReferenceResolver and it uses the default constructor of BidirectionalDictionary which uses EqualityComparer<T>.Default which, from MSDN documentation, uses the T's implementation of IEquatable<T> if it exists, or otherwise uses T's base.Equals() implementation.... all of this would lead me to believe that IEquatable in CarManufacturer should fix my problem. However, placing breakpoints in CarManufacturer.Equals() and GethashCode() never hit..
JSON.NET's logic for resolving references by default just compares references using this comparer.
If you want to compare objects in a different manner, you'll have to implement a custom IReferenceResolver.
Here's an example that takes an IEqualityComparer<T> to accommodate your use case:
public class ReferenceResolver<T> : IReferenceResolver
{
private Dictionary<string, T> stringToReference;
private Dictionary<T, string> referenceToString;
private int referenceCount;
public ReferenceResolver(IEqualityComparer<T> comparer)
{
this.stringToReference = new Dictionary<string, T>();
this.referenceToString = new Dictionary<T, string>(comparer);
this.referenceCount = 0;
}
public void AddReference(
object context,
string reference,
object value)
{
this.referenceToString.Add((T)value, reference);
this.stringToReference.Add(reference, (T)value);
}
public string GetReference(
object context,
object value)
{
string result = null;
if (!this.referenceToString.TryGetValue((T)value, out result))
{
referenceCount++;
result = referenceCount.ToString(CultureInfo.InvariantCulture);
this.referenceToString.Add((T)value, result);
this.stringToReference.Add(result, (T)value);
}
return result;
}
public bool IsReferenced(
object context,
object value)
{
return this.referenceToString.ContainsKey((T)value);
}
public object ResolveReference(
object context,
string reference)
{
T r = default(T);
this.stringToReference.TryGetValue(reference, out r);
return r;
}
}
Json.Net will call the Equals method on the objects being compared. In certain scenarios you may not want this however for example when it is checking for circular references it does the same whereas it may be more ideal to check for reference equality. They do this however to give the developer full control by overridding the Equals method in their classes.
You can override the default implementation. For example to make this a reference equality you would do the following:
var settings = new JsonSerializerSettings
{
EqualityComparer = new DefaultEqualityComparer(),
};
public class DefaultEqualityComparer : IEqualityComparer
{
public bool Equals(object x, object y)
{
return ReferenceEquals(x, y);
}
public int GetHashCode(object obj)
{
return obj.GetHashCode();
}
}

Categories

Resources