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'm storing users table-column-configuration in a simple class:
public class ColumnUserSetting : IComparable<ColumnUserSetting>
{
public String TableWrapperName { get; set; }
public String ColumnName { get; set; }
public Boolean Enabled { get; set; }
public int Width { get; set; }
public int Position { get; set; }
}
}
these classes are stored inside a SortedSet - so, it needed to implement IComparable<>, which i implemented based on position, as the documentation says its about position comparison - nothing said they can't be the same:
public class ColumnUserSetting : IComparable<ColumnUserSetting>
{
public String TableWrapperName { get; set; }
public String ColumnName { get; set; }
public Boolean Enabled { get; set; }
public int Width { get; set; }
public int Position { get; set; }
public int CompareTo(ColumnUserSetting other)
{
if (other.Position == this.Position) return 0;
if (other.Position > this.Position) return -1;
return 1;
}
}
However, this seems to behave like "equals" in the same run. Entries having the SAME Position are overwriting each other within the set. (Even if table an column is different)
the MSDN Docu says: "Types that implement IComparable must override Equals. Types that override Equals must also override GetHashCode; otherwise, Hashtable might not work correctly."
So, I implemented these two as well, with no success:
public class ColumnUserSetting : IComparable<ColumnUserSetting>
{
public String TableWrapperName { get; set; }
public String ColumnName { get; set; }
public Boolean Enabled { get; set; }
public int Width { get; set; }
public int Position { get; set; }
public int CompareTo(ColumnUserSetting other)
{
if (other.Position == this.Position) return 0;
if (other.Position > this.Position) return -1;
return 1;
}
public override bool Equals(object obj)
{
if (!(obj is ColumnUserSetting))
return false;
ColumnUserSetting cus = (ColumnUserSetting)obj;
return (this.TableWrapperName == cus.TableWrapperName &&
this.ColumnName == cus.TableWrapperName &&
this.Enabled == cus.Enabled &&
this.Width == cus.Width &&
this.Position == cus.Position);
}
public override int GetHashCode()
{
var hashcode = 352033288;
hashcode = hashcode * -1521134295 + TableWrapperName.GetHashCode();
hashcode = hashcode * -1521134295 + ColumnName.GetHashCode();
hashcode = hashcode * -1521134295 + Enabled.GetHashCode();
hashcode = hashcode * -1521134295 + Width.GetHashCode();
hashcode = hashcode * -1521134295 + Position.GetHashCode();
return hashcode;
}
}
Only way to get the SortedSet to work as expected was to handle entries of different tables with another result from CompareTo:
public int CompareTo(ColumnUserSetting other)
{
if (this.TableWrapperName != other.TableWrapperName)
return String.Compare(this.TableWrapperName, other.TableWrapperName);
if (other.Position == this.Position) return 0;
if (other.Position > this.Position) return -1;
return 1;
}
Is this a bug or a feature?
If we inspect the reference source code for SortedSet, we can look at the implementation of AddIfNotPresent(). This returns true if an item was added, or false if the item already exists.
Near the start of the method, we have:
int order = 0;
while (current != null) {
order = comparer.Compare(item, current.Item);
if (order == 0) {
// We could have changed root node to red during the search process.
// We need to set it to black before we return.
root.IsRed = false;
return false;
}
So it is only calling the Compare() method to see if the item is the same. Thus, for your class, it only cares if Position is the same. If it is, the new item is not added.
I would say that this is a deliberate design - it's not a bug.
You will have to change your CompareTo() implementation so that it compares all the same elements as the Equals(). Just call each element's CompareTo() to do a complete ordering.
This is my class.
public class Report
{
public string Name { get; set; }
public string City { get; set; }
public string Country { get; set; }
public string Lat { get; set; }
public string Long { get; set; }
public string Type { get; set; }
public DateTime CreateDate { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
return false;
var y = obj as Report;
if (y == null)
return false;
return
this.Name == y.Name &&
this.Long == y.Long &&
this.Lat == y.Lat;
}
public override int GetHashCode()
{
return (this.Name + this.Long + this.Lat).GetHashCode();
}
}
So this is my code and somehow non-unique values snick in when I create a new HashSet? Any ideas?
It looks like my post is mostly code so I need to add some more details.
I am creating the objects passed in the HashSet with this method (it is a console app made just for testing purposes, nothing fancy)
static Report CreateReport(dynamic report)
{
var result = new Report();
result.City = report.city.ToString();
result.Name = report.name.ToString();
result.Country = report.country.ToString();
result.Long = report.#long.ToString();
result.Lat = report.lat.ToString();
result.Type = report.type.ToString();
result.CreateDate = DateTime.Now;
return result;
}
Have you tried using an IEqualityComparer<T> where T is Report?
public class ReportComparer : IEqualityComparer<Report>
{
public override bool Equals(object obj)
{
if (obj == null)
return false;
var y = obj as Report;
if (y == null)
return false;
return
this.Name == y.Name &&
this.Long == y.Long &&
this.Lat == y.Lat;
}
public int GetHashCode(Report obj)
{
return (this.Name + this.Long + this.Lat).GetHashCode();
}
}
And then instantiate your HashSet with it:
HashSet<Report> reports = new HashSet<Report>(new ReportComparer())
Documentation:
MSDN EqualityComparer
I'm trying to create an audit-trail like order state history table. This way, Orders could have many OrderStates, and a single State which points to the most recent history item. So far so good when saving an updating. The problems arise when I try to query as if I was using an enum:
public class OrderState
{
public static OrderState Placed = new OrderState("Placed", 1, 1);
public static OrderState Accepted = new OrderState("Accepted", 10, 2);
public static OrderState Cancelled = new OrderState("Cancelled", 20, 3);
public static OrderState Completed = new OrderState("Completed", 30, 4);
protected OrderState()
{
}
public OrderState(string name, int order, int id)
{
Name = name;
Order = order;
Id = id;
}
public int Id { get; set; }
public string Name { get; protected set; }
public int Order { get; protected set; }
public static bool operator == (OrderState state1, OrderState state2)
{
if (ReferenceEquals(state1, null))
{
return ReferenceEquals(state2, null);
}
return state1.Equals(state2);
}
public static bool operator !=(OrderState state1, OrderState state2)
{
return !(state1 == state2);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (!(obj is OrderState))
{
return false;
}
return Equals((OrderState)obj);
}
public virtual bool Equals(OrderState other)
{
return other.Id.Equals(Id);
}
public override int GetHashCode()
{
unchecked
{
return ((Id.GetHashCode())*397) ^ Order;
}
}
}
Order class
public class Order
{
public Order()
{
Progress(OrderState.Placed);
}
public int Id { get; set; }
public virtual OrderState State
{
get { return States.OrderByDescending(x => x.State.Order).FirstOrDefault()?.State; }
}
public void Progress(OrderState state)
{
if (States.All(x => x.State != state))
{
States.Add(new OrderStateHistory()
{
Order = this,
State = state
});
}
}
public virtual ICollection<OrderStateHistory> States { get; set; } = new List<OrderStateHistory>();
}
In my code, things like these work fine:
order.Progress(OrderState.Accepted);, if (order.State == OrderState.Accepted)
However, what I'd like to get to is Where(x => x.State.Equals(OrderState.Accepted)) or Where(x => x.State == OrderState.Accepted)
Unfortunately, either of the criterias will yield an 'The specified type member 'State' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.' error.
I know I have been able to do this with NHibernate. Can I even do this with EF?
Since EF needs to translate your LINQ statements to SQL statements,
you can't do this. If you have complex comparison logic in your
overridden Equals() method you will have to duplicate that in the
LINQ statement.
public IQueryable<Foo> FoosEqualTo(IQueryable<Foo> allFoos, Foo target) {
return from foo in allFoos
where foo.Id == target.Id // or other comparison logic...
select foo;
}
public Foo getFoo(Foo target) {
return FoosEqualTo(DC.foos, target).FirstOrDefault();
}
What would be the best (most elegant or performing) way of overloading the equality operator on a class containing only string attributes?
Example:
class MagicClass
{
public string FirstAttribute { get; set; }
public string SecondAttribute { get; set; }
public string ThirdAttribute { get; set; }
public string FourthAttribute { get; set; }
public string FifthAttribute { get; set; }
}
I know how to overload the operator itself, however, I am wondering about the following points:
Is there a way to elegantly compare such two objects (e.g. without having to write an if statement containing mutual comparisons of all the attributes
What would be a good implementation of the GetHashCode() method in such case
How about something like this, Just create array of all properties and a loop.
internal class MagicClass
{
public string FirstAttribute { get; set; }
public string SecondAttribute { get; set; }
public string ThirdAttribute { get; set; }
public string FourthAttribute { get; set; }
public string FifthAttribute { get; set; }
private string[] AllProperties//Array of all properties
{
get
{
return new[]
{
FirstAttribute,
SecondAttribute,
ThirdAttribute,
FourthAttribute,
FifthAttribute
};
}
}
protected bool Equals(MagicClass other)
{
var thisProps = this.AllProperties;
var otherProps = other.AllProperties;
return thisProps.SequenceEqual(otherProps);
}
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((MagicClass) obj);
}
public override int GetHashCode()
{
unchecked
{
var thisProps = this.AllProperties;
int hashCode = 0;
foreach (var prop in thisProps)
{
hashCode = (hashCode * 397) ^ (prop != null ? prop.GetHashCode() : 0);
}
return hashCode;
}
}
}
Then you can call Equals method inside your operator overload. If you're lazy to create AllProperties array you can use Reflection but IMO reflection is overkill here.
Not saying this is the 'best' or the most elegant solution, but I'd have the tendency to use an array and an index initializer, using an enumeration, so I could reuse get and set logic and in this case reset a hash code for a quick first comparison.
The advantage of the enumeration is, that you don't have to recheck your compare logic when an attribute is added, and you can prevent the overhead of resorting to reflection.
class MagicClass
{
string[] Values = new string[Enum.GetValues(typeof(MagicClassValues)).Length];
public string this[MagicClassValues Value] //and/or a GetValue/SetValue construction
{
get
{
return Values[(int)Value];
}
set
{
Values[(int)Value] = value;
hash = null;
}
}
int? hash; //buffered for optimal dictionary performance and == comparisson
public override int GetHashCode()
{
if (hash == null)
unchecked
{
hash = Values.Sum(s => s.GetHashCode());
}
return hash.Value;
}
public static bool operator ==(MagicClass v1, MagicClass v2) //used == operator, in compliance to the question, but this would be better for 'Equals'
{
if(ReferenceEquals(v1,v2))return true;
if(ReferenceEquals(v1,null) || ReferenceEquals(v2,null) || v1.GetHashCode() != v2.GetHashCode())return false;
return v1.Values.SequenceEqual(v2.Values);
}
public static bool operator !=(MagicClass v1, MagicClass v2)
{
return !(v1 == v2);
}
//optional, use hard named properties as well
public string FirstAttribute { get { return this[MagicClassValues.FirstAttribute]; } set { this[MagicClassValues.FirstAttribute] = value; } }
}
public enum MagicClassValues
{
FirstAttribute,
SecondAttribute,
//etc
}
I have an Entity Framework Entity that looks something like this:
class ListItemEtlObject
{
public int ID { get; set; }
public string ProjectName { get; set; }
public string ProjectType { get; set; }
public string ProjectCode { get; set; }
public string ProjectDescription { get; set; }
public string JobNo { get; set; }
public string JobDescription { get; set; }
public bool Include { get; set; }
}
I am pulling items from two different data sources into IEnumerable lists. How might I go about comparing the items without using a bunch of if statements to check if there are differences between the properties' values and then set the property's value if they do not match? The idea is to keep the lists synchronized. Also list A has an ID value set, list B does not. I just feel there is a better way to do this than a bunch of
if(objectA.ProjectName != objectB.ProjectName)
{
objectA.ProjectName = objectB.ProjectName;
}
If you have control of the source object then the best declarative way to support value based equality is to implement IEquatable<T>. This does unfortunately require you to enumerate out all of those checks but it's done once at the actual object definition location and doesn't need to be repeated throughout the code base.
class ListItemEtlObject : IEquatable<ListITemEtlObject>
{
...
public void Equals(ListITemEtlObject other) {
if (other == null) {
return false;
}
return
ID == other.ID &&
ProjectName == other.ProjectName &&
ProjectType == other.ProjectType &&
... ;
}
}
Additionally you could choose to overload the equality operator on the object type and allow consumers to simply use != and == on ListItemEtlObject instances and get value equality instead of reference equality.
public static bool operator==(ListItemEtlObject left, ListItemEtlObject right) {
return EqualityComparer<ListItemEtlObject>.Default.Equals(left, right);
}
public static bool operator!=(ListItemEtlObject left, ListItemEtlObject right) {
return !(left == right);
}
The easiest way would be to provide a method on your class that computes a specific hash, much like GetHashCode, and then if two instances compute the same hash, they can be said to be equivalent.
You could simplify it using reflection =)
public virtual void SetDifferences(MyBaseClass compareTo)
{
var differences = this.GetDifferentProperties(compareTo);
differences.ToList().ForEach(x =>
{
x.SetValue(this, x.GetValue(compareTo, null), null);
});
}
public virtual IEnumerable<PropertyInfo> GetDifferentProperties(MyBaseClass compareTo)
{
var signatureProperties = this.GetType().GetProperties();
return (from property in signatureProperties
let valueOfThisObject = property.GetValue(this, null)
let valueToCompareTo = property.GetValue(compareTo, null)
where valueOfThisObject != null || valueToCompareTo != null
where (valueOfThisObject == null ^ valueToCompareTo == null) || (!valueOfThisObject.Equals(valueToCompareTo))
select property);
}
And here are a couple of tests I did for you
[TestMethod]
public void CheckDifferences()
{
var f = new OverridingGetHashCode();
var g = new OverridingGetHashCode();
f.GetDifferentProperties(g).Should().NotBeNull().And.BeEmpty();
f.Include = true;
f.GetDifferentProperties(g).Should().NotBeNull().And.HaveCount(1).And.Contain(f.GetType().GetProperty("Include"));
g.Include = true;
f.GetDifferentProperties(g).Should().NotBeNull().And.BeEmpty();
g.JobDescription = "my job";
f.GetDifferentProperties(g).Should().NotBeNull().And.HaveCount(1).And.Contain(f.GetType().GetProperty("JobDescription"));
}
[TestMethod]
public void SetDifferences()
{
var f = new OverridingGetHashCode();
var g = new OverridingGetHashCode();
g.Include = true;
f.SetDifferences(g);
f.GetDifferentProperties(g).Should().NotBeNull().And.BeEmpty();
f.Include = true;
g.Include = false;
f.SetDifferences(g);
f.GetDifferentProperties(g).Should().NotBeNull().And.BeEmpty();
f.Include.Should().BeFalse();
}