This question already has answers here:
What's the best strategy for Equals and GetHashCode?
(7 answers)
Closed 9 years ago.
I have never really done this before so i was hoping that someone could show me the correct what of implementing a override of Except() and GetHashCode() for my class.
I'm trying to modify the class so that i can use the LINQ Except() method.
public class RecommendationDTO{public Guid RecommendationId { get; set; }
public Guid ProfileId { get; set; }
public Guid ReferenceId { get; set; }
public int TypeId { get; set; }
public IList<TagDTO> Tags { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public bool IsActive { get; set; }
public object ReferencedObject { get; set; }
public bool IsSystemRecommendation { get; set; }
public int VisibilityScore { get; set; }
public RecommendationDTO()
{
}
public RecommendationDTO(Guid recommendationid,
Guid profileid,
Guid referenceid,
int typeid,
IList<TagDTO> tags,
DateTime createdon,
DateTime modifiedon,
bool isactive,
object referencedobject)
{
RecommendationId = recommendationid;
ProfileId = profileid;
ReferenceId = referenceid;
TypeId = typeid;
Tags = tags;
CreatedOn = createdon;
ModifiedOn = modifiedon;
ReferencedObject = referencedobject;
IsActive = isactive;
}
public override bool Equals(System.Object obj)
{
// If parameter is null return false.
if (obj == null)
{
return false;
}
// If parameter cannot be cast to Point return false.
RecommendationDTO p = obj as RecommendationDTO;
if ((System.Object)p == null)
{
return false;
}
// Return true if the fields match:
return (ReferenceId == p.ReferenceId);// && (y == p.y);
}
public bool Equals(RecommendationDTO p)
{
// If parameter is null return false:
if ((object)p == null)
{
return false;
}
// Return true if the fields match:
return (ReferenceId == p.ReferenceId);// && (y == p.y);
}
//public override int GetHashCode()
//{
// return ReferenceId;// ^ y;
//}}
I have taken a look at http://msdn.microsoft.com/en-us/library/ms173147.aspx but i was hoping someone could show me within my own example.
Any help would be appreciated.
Thank you
You can override Equals() and GetHashCode() on your class like this:
public override bool Equals(object obj)
{
var item = obj as RecommendationDTO;
if (item == null)
{
return false;
}
return this.RecommendationId.Equals(item.RecommendationId);
}
public override int GetHashCode()
{
return this.RecommendationId.GetHashCode();
}
public override bool Equals(System.Object obj)
{
// Check if the object is a RecommendationDTO.
// The initial null check is unnecessary as the cast will result in null
// if obj is null to start with.
var recommendationDTO = obj as RecommendationDTO;
if (recommendationDTO == null)
{
// If it is null then it is not equal to this instance.
return false;
}
// Instances are considered equal if the ReferenceId matches.
return this.ReferenceId == recommendationDTO.ReferenceId;
}
public override int GetHashCode()
{
// Returning the hashcode of the Guid used for the reference id will be
// sufficient and would only cause a problem if RecommendationDTO objects
// were stored in a non-generic hash set along side other guid instances
// which is very unlikely!
return this.ReferenceId.GetHashCode();
}
Be careful when using a primary key as your test for equality in overriding Equals() because it only works AFTER the object has been persisted. Prior to that your objects don't have primary keys yet and the IDs of the ones in memory are all zero.
I use base.Equals() if either of the object IDs is zero but there probably is a more robust way.
Related
I have these classes:
public class AlertEvaluation
{
public string AlertId { get; set; }
public ICollection<EvaluatedTag> EvaluatedTags { get; set; }
public string TransactionId { get; set; }
public EvaluationStatus EvaluationStatus { get; set; }
public DateTime EvaluationDate { get; set; }
}
public class EvaluatedTag
{
public string Id { get; set; }
public string Name { get; set; }
}
And I would like to get a list of alert evaluations grouped by AlertId, and by EvaluatedTags, meaning that I would like to compare and group evaluations that not only have the same AlertId, but to also have the same list of EvaluatedTags. (And also get the last evaluation in time)
I tried this:
var evaluationsGroupedAndOrdered = evaluations.GroupBy(x => new { x.AlertSettingId, x.EvaluatedLabels })
.Select(x => x.OrderByDescending(z => z.EvaluationDate ).FirstOrDefault()).ToList();
But of course, the comparing of list properties like that did not work.
I read something about adding an equality comparer in GroupBy, which would mean comparing the lists inside the objects right? But I'm not sure of how to implement it in the right way.
I tried (based on GroupBy on complex object (e.g. List<T>)) :
public class AlertEvaluationComparer : IEqualityComparer<AlertEvaluation>
{
public bool Equals(AlertEvaluation x, AlertEvaluation y)
{
return x.AlertId == y.AlertId && x.EvaluatedTags.OrderBy(val => val.Name).SequenceEqual(y.EvaluatedTags.OrderBy(val => val.Name));
}
public int GetHashCode(AlertSettingEvaluation x)
{
return x.AlertId.GetHashCode() ^ x.EvaluatedTags.Aggregate(0, (a, y) => a ^ y.GetHashCode());
}
}
But did not work either.. Maybe because my list EvaluatedTags is not a list of strings but of individual objects.
Does anybody have a nice solution for this?
A typical way to compare two lists is to use the System.Linq exension method, SequenceEquals. This method returns true if both lists contain the same items, in the same order.
In order to make this work with an IEnumerable<EvaluatedTag>, we need to have a way to compare instances of the EvaluatedTag class for equality (determining if two items are the same) and for sorting (since the lists need to have their items in the same order).
To do this, we can override Equals and GetHashCode and implement IComparable<EvaluatedTag> (and might as well do IEquatable<EvaluatedTag> for completeness):
public class EvaluatedTag : IEquatable<EvaluatedTag>, IComparable<EvaluatedTag>
{
public string Id { get; set; }
public string Name { get; set; }
public int CompareTo(EvaluatedTag other)
{
if (other == null) return -1;
var result = string.CompareOrdinal(Id, other.Id);
return result == 0 ? string.CompareOrdinal(Name, other.Name) : result;
}
public bool Equals(EvaluatedTag other)
{
return other != null &&
string.Equals(other.Id, Id) &&
string.Equals(other.Name, Name);
}
public override bool Equals(object obj)
{
return Equals(obj as EvaluatedTag);
}
public override int GetHashCode()
{
return Id.GetHashCode() * 17 +
Name.GetHashCode() * 17;
}
}
Now we can use this in the custom comparer you have in your question, for sorting and comparing the EvaluatedTags:
public class AlertEvaluationComparer : IEqualityComparer<AlertEvaluation>
{
// Return true if the AlertIds are equal, and the EvaluatedTags
// contain the same items (call OrderBy to ensure they're in
// the same order before calling SequenceEqual).
public bool Equals(AlertEvaluation x, AlertEvaluation y)
{
if (x == null) return y == null;
if (y == null) return false;
if (!string.Equals(x.AlertId, y.AlertId)) return false;
if (x.EvaluatedTags == null) return y.EvaluatedTags == null;
if (y.EvaluatedTags == null) return false;
return x.EvaluatedTags.OrderBy(et => et)
.SequenceEqual(y.EvaluatedTags.OrderBy(et => et));
}
// Use the same properties in GetHashCode that were used in Equals
public int GetHashCode(AlertEvaluation obj)
{
return obj.AlertId?.GetHashCode() ?? 0 * 17 +
obj.EvaluatedTags?.Sum(et => et.GetHashCode() * 17) ?? 0;
}
}
And finally we can pass your AlertEvaluationComparer to the GroupBy method to group our items:
var evaluationsGroupedAndOrdered = evaluations
.GroupBy(ae => ae, new AlertEvaluationComparer())
.OrderBy(group => group.Key.EvaluationDate)
.ToList();
Here's a go at it, getting away from Linq a bit to make it easier to build the groups one at a time while leveraging sorting:
// Build groups by using a combination of AlertId and EvaluatedTags hashcode as group key
var groupMap = new Dictionary<string, SortedSet<AlertEvaluation>>();
foreach (var item in evals)
{
var combinedKey = item.AlertId + EvaluatedTag.GetCollectionHashCode(item.EvaluatedTags);
if (groupMap.TryGetValue(combinedKey, out SortedSet<AlertEvaluation>? groupItems))
{
// Add to existing group
groupItems.Add(item);
}
else
{
// Create new group
groupMap.Add(combinedKey, new SortedSet<AlertEvaluation> { item });
}
}
// Get a list of groupings already sorted ascending by EvaluationDate
List<SortedSet<AlertEvaluation>>? groups = groupMap.Values.ToList();
This assumes that the classes implement IComparable and Equals/GetHashCode to facilitate sorting:
public class AlertEvaluation : IComparable<AlertEvaluation>
{
public string AlertId { get; set; }
public ICollection<EvaluatedTag> EvaluatedTags { get; set; }
public string TransactionId { get; set; }
public EvaluationStatus EvaluationStatus { get; set; }
public DateTime EvaluationDate { get; set; }
// Used by SortedSet
public int CompareTo(AlertEvaluation? other)
{
if (other is null)
{
return 1;
}
return EvaluationDate.CompareTo(other.EvaluationDate);
}
}
public class EvaluatedTag : IEquatable<EvaluatedTag?>
{
public string Id { get; set; }
public string Name { get; set; }
public bool Equals(EvaluatedTag? other) => other != null && Id == other.Id && Name == other.Name;
public override int GetHashCode() => HashCode.Combine(Id, Name);
// Helper to get a hash of item collection
public static int GetCollectionHashCode(ICollection<EvaluatedTag> items)
{
var code = new HashCode();
foreach (var item in items.OrderBy(i => i.Id))
{
code.Add(item);
}
return code.ToHashCode();
}
}
By the way, I'm using the fancy new HashCode class in .NET Core to override hash codes.
I am trying to compare two hashsets of Definition type as EqualityComparer<T>.Default.Equals(value, oldValue). Definition is defined as follows
public class Definition
{
public string Variable { get; set; }
public HashSet<Location> LocationList { get; set; }
public override bool Equals(object obj)
{
Definition other = obj as Definition;
return other.Variable.Equals(this.Variable) && other.LocationList!= null &&this.LocationList != null
&& other.LocationList.Count == this.LocationList.Count
&& other.LocationList == this.LocationList;
}
public override int GetHashCode()
{
return this.Variable.GetHashCode() ^ this.LocationList.Count.GetHashCode();// ^ this.LocationList.GetHashCode();
}
}
public class Location
{
public int Line { get; set; }
public int Column { get; set; }
public int Position { get; set; }
public string CodeTab { get; set; }
public Location(int line, int col, int pos, string tab)
{
Line = line;
Column = col;
Position = pos;
CodeTab = tab;
}
public override bool Equals(object obj)
{
Location other = obj as Location;
return this.CodeTab == other.CodeTab
&& this.Position == other.Position
&& this.Column == other.Column
&& this.Line == other.Line;
}
public override int GetHashCode()
{
return this.CodeTab.GetHashCode() ^ this.Position.GetHashCode()
^ this.Column.GetHashCode() ^ this.Line.GetHashCode();
}
}
Somehow for a similar set, the result is returned as false despite all the information remaining the same. The only difference is that the position of some elements are interchanged, but I know that HashSet won't preserve or check the order while comparing. Can any one advise me on what is going wrong here?
PS: I tried uncommenting this.LocationList.GetHashCode() also, but didn't work.
You need to create a comparer for the sets:
var setComparer = HashSet<Location>.CreateSetComparer();
return other.Variable.Equals(this.Variable) && setComparer.Equals(this.LocationList, other.LocationList);
EqualityComparer<T>.Default will look for an object implementing IEquatable<T>. Otherwise, it will defer to an ObjectEqualityComparer, which simply checks for reference equality. This is why you're seeing false when references are compared.
What you actually want to do is explicitly implement IEquatable<Location>. Note you should really make your properties immutable for this to work properly:
public class Location : IEquatable<Location>
{
public Location(int line, int col, int pos, string tab)
{
Line = line;
Column = col;
Position = pos;
CodeTab = tab;
}
public int Line { get; private set; }
public int Column { get; private set; }
public int Position { get; private set; }
public string CodeTab { get; private set; }
public bool Equals(Location other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(CodeTab, other.CodeTab) && Column == other.Column && Line == other.Line && Position == other.Position;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Location) obj);
}
public static bool operator ==(Location left, Location right)
{
return Equals(left, right);
}
public static bool operator !=(Location left, Location right)
{
return !Equals(left, right);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = (CodeTab != null ? CodeTab.GetHashCode() : 0);
hashCode = (hashCode*397) ^ Column;
hashCode = (hashCode*397) ^ Line;
hashCode = (hashCode*397) ^ Position;
return hashCode;
}
}
}
Now if you look at the type EqualityComparer created by Default, you'll see GenericEqualityComparer<Location>:
Console.WriteLine(EqualityComparer<Location>.Default.GetType())
I have a Product class,
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string ModelNumber { get; set; }
public string Sku { get; set; }
public string Description { get; set; }
public double Price { get; set; }
public double NewPrice { get; set; }
}
I am saving this class in my database table. But I also need to save the hash of the each object in my database table for change tracking. What I am looking is that,
var p1 = new Product{
Id =2,
Name = "1",
ModelNumber = "4"
};
var p2 = new Product
{
Id = 2,
Name = "1",
ModelNumber = "4"
};
var hashOfp1 = Hash(p1);
var hashOfp2 = Hash(p2);
// This should return true because both objects have same values
if(hashOfp1 == hashOfp2){
}
A good for this kind of things is :
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string ModelNumber { get; set; }
public string Sku { get; set; }
public string Description { get; set; }
public double Price { get; set; }
public double NewPrice { get; set; }
public override int GetHashCode()
{
return Id ^ (Name ?? "").GetHashCode() ^ (ModelNumber ?? "").GetHashCode() ^ (Sku ?? "").GetHashCode()^ (Description ?? "").GetHashCode() ^ Price.GetHashCode() ^ NewPrice.GetHashCode();
}
}
The idea is to combine hashes of all sub properties... you could choose any combination you wish, but "xor" operator is quite a good choice because it prevents your hash to tend to something as you add new properties to your hash (like a "&" or "+" operator would)
edit
Quick and dirty explanation for the "tend to something" part, as requested:
Let's assume you chose A & B & C & D ... to hash values A, B, C etc.
The most properties you add to your hash, the more chance you will have to have a huge hash that tends to int.MaxValue (which corresponds to 11111111111111111111111111111111, binary)
Same thing with "+"... your hashes will get bigger and bigger, not using the full spectrum of int values.
... A good hash algorithm is just about maximizing the chances to have a different hash for different values. To do that, you can either study how values are used in real life (can be painful and overkill), or maximize the spectrum of int values that your hash algorithm covers for random unknown values.
You should override the GetHashCode() method of the base class Object.
In this overriden method you can create a hash code based on the Id or other properties.
You can use it then like:
var hashOfp1 = p1.GetHashCode();
If you override GetHashCode() you should also override Equals(object). Code courtesy of ReSharper
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string ModelNumber { get; set; }
public string Sku { get; set; }
public string Description { get; set; }
public double Price { get; set; }
public double NewPrice { get; set; }
protected bool Equals(Product other)
{
return Id == other.Id && string.Equals(Name, other.Name) &&
string.Equals(ModelNumber, other.ModelNumber) &&
string.Equals(Sku, other.Sku) && string.Equals(Description, other.Description) &&
Price.Equals(other.Price) && NewPrice.Equals(other.NewPrice);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj.GetType() != this.GetType())
{
return false;
}
return Equals((Product) obj);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = Id;
hashCode = (hashCode*397) ^ (Name != null ? Name.GetHashCode() : 0);
hashCode = (hashCode*397) ^ (ModelNumber != null ? ModelNumber.GetHashCode() : 0);
hashCode = (hashCode*397) ^ (Sku != null ? Sku.GetHashCode() : 0);
hashCode = (hashCode*397) ^ (Description != null ? Description.GetHashCode() : 0);
hashCode = (hashCode*397) ^ Price.GetHashCode();
hashCode = (hashCode*397) ^ NewPrice.GetHashCode();
return hashCode;
}
}
}
I have a GroupSummary class that has some properties like this in it:
public class GroupsSummary
{
public bool FooMethod()
{
////
}
public bool UsedRow { get; set; }
public string GroupTin { get; set; }
public string PayToZip_4 { get; set; }
public string PayToName { get; set; }
public string PayToStr1 { get; set; }
public string PayToStr2 { get; set; }
public string PayToCity { get; set; }
public string PayToState { get; set; }
public bool UrgentCare_YN { get; set; }
}
Then I have a Dictionary like <string, List<GroupsSummary>
For each of these dictionary items I want to find all the distinct addresses but the properties of this class that define a distinct address for me are
PayToStr1,PayToStr2,PayToCity,PayToState
I know as far as I can say something like mydictionartItem.select(t => t).Distinct().ToList() but I think that will compare all the properties of this class which is wrong. So how should I solve this?
var newDict = dict.ToDictionary(
x=>x.Key,
v=>v.Value.GroupBy(x=>new{x.PayToStr1, x.PayToStr2, x.PayToCity, x.PayToState})
.Select(x=>x.First())
.ToList());
implement IEquatable<T> interface on the GroupsSummary Class. More information can be found here
IEquatable
defines a method Equals. Remember to overload the GetHashCode method as well
Write your own IEqualityComparer, like so:
public class GroupsSummaryComparer : IEqualityComparer<GroupsSummary>
{
public bool Equals(GroupsSummary x, GroupsSummary y)
{
if (object.ReferenceEquals(x, y))
return true;
else if (object.ReferenceEquals(x, null) || object.ReferenceEquals(y, null))
return false;
return x.PayToStr1 == y.PayToStr1 && x.PayToStr2 == y.PayToStr2 && x.PayToCity == y.PayToCity && x.PayToState == y.PayToState;
}
public int GetHashCode(GroupsSummary obj)
{
if (obj == null)
return 0;
int code;
if (obj.PayToStr1 != null)
code ^= obj.PayToStr1.GetHashCode();
if (obj.PayToStr2 != null)
code ^= obj.PayToStr2.GetHashCode();
if (obj.PayToCity != null)
code ^= obj.PayToCity.GetHashCode();
if (obj.PayToState != null)
code ^= obj.PayToState.GetHashCode();
return code;
}
}
Then you can pass it to Distinct
This may be safer than implementing IEquatable<GroupsSummary> directly on the class, since, in other situations, you may want to test them for full equality.
The easiest way would be to implement an IEqualityComparer<GroupsSummary>
Then you can say something like
HashSet<GroupSummary> unique = new HashSet<GroupsSummary>(
myDict.Values ,
new MyGroupsSummaryEqualityComparer()
) ;
here is the object code:
public class DlpItem : IEqualityComparer<DlpItem>
{
public string Text { get; set; }
public int Id { get; set; }
public DlpItem(int pId)
{
Text = string.Empty;
Id = pId;
}
public override bool Equals(object obj)
{
return Id == (obj as DlpItem).Id;
}
public bool Equals(DlpItem a, DlpItem b)
{
return a.Id == b.Id;
}
public int GetHashCode(DlpItem item)
{
return Id.GetHashCode();
}
}
And I have two lists as follows:
var list1 = new List<DlpItem>();
list1.Add(new DlpItem(1));
list1.Add(new DlpItem(2));
var list2 = new List<DlpItem>();
list2.Add(new DlpItem(1));
list2.Add(new DlpItem(2));
var delItems = list1.Except(list2).ToList<DlpItem>();
delItems always has both items in it. What am I missing here?
EDIT: Code now implements IEquatable
public class DlpItem : IEqualityComparer<DlpItem>, IEquatable<DlpItem>
{
public string Text { get; set; }
public int Id { get; set; }
public override bool Equals(object obj)
{
return Id - (obj as DlpItem).Id == 0;
}
public bool Equals(DlpItem a, DlpItem b)
{
return a.Id == b.Id;
}
public bool Equals(DlpItem item)
{
return item != null && Id == item.Id;
}
public int GetHashCode(DlpItem item)
{
return Id.GetHashCode();
}
}
In your example, you don't actually add anything to list2... a simple enough mistake, but there is a more significant issue:
It needs to be IEquatable<T> not an IEqualityComparer<T>; also, you might want to ensure the hashcode can't change; most simply by making Id read-only:
public class DlpItem : IEquatable<DlpItem>
{
public string Text { get; set; }
private readonly int id;
public int Id { get { return id; } }
public DlpItem(int id)
{
Text = "";
this.id = id;
}
public override bool Equals(object obj)
{
return Equals(obj as DlpItem);
}
public bool Equals(DlpItem other)
{
return other != null && this.Id == other.Id;
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
}