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
}
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.
I red a few articles on internet but all value to me, I couldn't understand how can I avoid adding a duplicate object to a list, I tried something like this.
I actually have created a class which overrides GetHashCode and Equal method.
Now I want to form a collection of non duplicate object list.
public class FlightInfo
{
public string Origin { get; set; }
public string DepartureTime { get; set; }
public string Destination { get; set; }
public string DestinationTime { get; set; }
public string Price { get; set; }
public override bool Equals(object obj)
{
var other = obj as FlightInfo;
if (other == null)
return false;
if (Origin != other.Origin || DepartureTime != other.DepartureTime || Destination != other.Destination
|| DestinationTime != other.DestinationTime || Price != other.Price)
return false;
return true;
}
public override int GetHashCode()
{
int hashOrigin = Origin.GetHashCode();
int hashDestination = Destination.GetHashCode();
int hashDepartureTime = DepartureTime.GetHashCode();
int hashDestinationTime = DestinationTime.GetHashCode();
int hashPrice = Price.GetHashCode();
return hashOrigin ^ hashDestination ^ hashDepartureTime ^ hashDestinationTime ^ hashPrice;
}
}
I also tried one article by Eric
https://blogs.msdn.microsoft.com/ericlippert/2011/02/28/guidelines-and-rules-for-gethashcode/
but this article has
private List<T>[] buckets = new List<T>[100];
insead of private List<T>() buckets = new List<T>()
but I want to return a list with no fix size.
Since you already implemented the Equals and GetHashCode methods you can have your own custom list of FlightInfo that will make use of those methods:
public class FlightInfoList : IList<FlightInfo>
{
private readonly List<FlightInfo> _flightInfos = new List<FlightInfo>();
public IEnumerator<FlightInfo> GetEnumerator()
{
return _flightInfos.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(FlightInfo item)
{
if (_flightInfos.Any(flightInfo => flightInfo.Equals(item)))
{
throw new Exception("Cannot add duplicated values!");
}
_flightInfos.Add(item);
}
public void Clear()
{
_flightInfos.Clear();
}
public bool Contains(FlightInfo item)
{
return _flightInfos.Contains(item);
}
public void CopyTo(FlightInfo[] array, int arrayIndex)
{
_flightInfos.CopyTo(array, arrayIndex);
}
public bool Remove(FlightInfo item)
{
return _flightInfos.Remove(item);
}
public int Count => _flightInfos.Count;
public bool IsReadOnly => false;
public int IndexOf(FlightInfo item)
{
return _flightInfos.IndexOf(item);
}
public void Insert(int index, FlightInfo item)
{
_flightInfos.Insert(index, item);
}
public void RemoveAt(int index)
{
_flightInfos.RemoveAt(index);
}
public FlightInfo this[int index]
{
get => _flightInfos[index];
set => _flightInfos[index] = value;
}
}
Notice that in the Add method I'm checking if there's a duplicated. Another way to solve this is to use a dictionary.
One of the columns in my DevExpress xtragrid is not sorting, grouping or filtering. Answers to similar questions suggest I need to implement IComparable, but when I did that it no longer displays in the column at all.
public class Flow : System.IComparable<Flow>
{
public Flow(int id, string name, string description)
{
this.ID = id;
this.Name = name;
this.Description = description;
}
public int ID { get; private set; }
public string Name { get; private set; }
public string Description { get; private set; }
public override string ToString()
{
return Name;
}
public override bool Equals(object obj)
{
Flow flow = obj as Flow;
if (flow == null) return false;
return this.ID == flow.ID;
}
public static bool operator ==(Flow flow1, Flow flow2)
{
if (object.ReferenceEquals(null, flow1))
return object.ReferenceEquals(null, flow2);
return flow1.Equals(flow2);
}
public static bool operator !=(Flow flow1, Flow flow2)
{
return !(flow1 == flow2);
}
public override int GetHashCode()
{
return ID;
}
public int CompareTo(Flow other)
{
return this.Name.CompareTo(other.Name);
}
}
What have I done wrong?
UPDATE:
Asked on DevExpress...
The disappearing content was an unrelated issue - a red herring. The column allowed sorting once I had implemented IComparable rather than IComparable<Flow>
public int CompareTo(object obj)
{
if (object.ReferenceEquals(null, obj))
return 1;
Flow flow = obj as Flow;
if (flow == null)
throw new ArgumentException("Object is not of type Flow");
return this.Name.CompareTo(flow.Name);
}
Sourced from MSDN documentation for IComparable.CompareTo Method
It looks like your CompareTo Method is wrong. Try adding the following to the CompareTo() Method and see if it's working:
public int CompareTo(Flow other)
{
// Alphabetic sort if name is equal.
if this.Name == other.Name
{
return this.Name.CompareTo(other.Name);
}
//Default sort.
return other.Name.CompareTo(this.Name);
}
Let me know if it sorted out your problem.
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();
}
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.