Hi I am trying to add in IEquatable to my program and I have no clue if I need to add a unique id basicly and a hashcode? They are using the shd number as a unique id in the IEquatable but they give the value to it in the constructor and I asked on this site if the constructor was needed to look like it does in the documentation and I got a no. So now I am confused can someone give me an easier example of IEquatable then the documentation does? here is the link for the documentation https://learn.microsoft.com/en-us/dotnet/api/system.iequatable-1?view=netcore-3.1 I am just trying to get contains for a custom object list to work.
My code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DataConverter.Objects
{
public class Category : IEquatable<Category>
{
public string _Name { get; set; }
public string _Id { get; set;}
private string _HomeCategory { get; set; }
public string _prestaId { get; set; }
public bool _inUse { get; set; }
public Category(string Name, string Id, string HomeCategory, bool inUse)
{
_Name = Name;
_Id = Id;
_HomeCategory = HomeCategory;
_inUse = inUse;
}
public bool Equals(Category other)
{
if (other == null)
return false;
if()
}
}
}
IEquatable<T> is just an interface that tells you that the current type has value equality semantics, not reference equality. What the logic is behind what makes two distinct objets equal or not is completely dependant on the business logic of your application, not how you correctly or incorrectly implement the interface.
What makes two categories be the same in your domain? The same Id? Then your equals method would be:
public bool Equals(Category other)
{
if (other == null)
return false;
return _Id == other._Id;
}
The same Id and name?
public bool Equals(Category other)
{
if (other == null)
return false;
return _Id == other._Id &&
_Name == other._Name;
}
And so on. Its you who decides what makes two categories equal each other.
Now, about the hash code. The basic rule a hash code must comply with is: Two categories that are equal must have the same hash code. Do note that this does not mean that two categories with the same hash code are equal, hash codes can collide, there is no problem with that at all. Also, hash codes should not change if the object changes, so ideally they should be built upon data that can not be changed.
Ok, so how would you implement your hash code in your particular scenario?
Case 1:
public override int GetHashCode() => _Id.GetHashCode();
Case 2:
public override int GetHashCode() => _Id.GetHashCode() ^
_Name.GetHashCode();
Both hashes will be consistent with their Equals counterpart. The only problem is that they are built on potentially mutable data (both Id and Name have setters) so if any of those change, your haschcode would change too, and that is usually a bad idea.
Depending on your scenario you might have to deal with this problem differently. Is your data likely to mutate while performing any hash sensitive operations? Linq for example used hashes quite extensively...
Related
Consider a simple POCS object FeeInfo (yes I know an object would normally be called simply Fee, but the usage of it makes the class name FeeInfo make more sense.)
public FeeInfo()
{
Description = String.Empty;
Status = String.Empty;
}
public string Description { get; set; }
public string Status { get; set; }
public decimal FeeAmount { get; set; }
public decimal FeePaid { get; set; }
public bool Equals(FeeInfo targetFee)
{
if (targetFee.Description == this.Description && targetFee.Status == this.Status &&
targetFee.FeeAmount == this.FeeAmount && targetFee.FeePaid == this.FeePaid)
{
return true;
}
return false;
}
I am clearly not using the interfaces for comparing / equality (largely because I don't yet understand them enough to understand if I need/want them. For this object, my equality method works to my needs.
So imagine another object FeesInfo. Where FeesInfo wraps
private readonly List<FeeInfo> _fees = new List<FeeInfo>();
implements :IEnumerable and the like.
I want to be able to compare two FeesInfo objects to ensure that the FeeInfo objects within it are the same. This would be two instances which contain the same number (Count) of elements which is easy. But I want an unordered comparison of the objects whose FeeInfo properties are the same.
What is the best way to achieve this for this not super savy c# programmer? Thanks
The COMMENT is not an "answer" exactly. But it is THE answer.
First fix your implementation of Equals on your FeeInfo class. You may want to follow this guide: How to define value equality for a class or struct (C# Programming Guide)
This question already has answers here:
LINQ Intersect not returning items
(2 answers)
Closed 1 year ago.
I need to have instances of a DispenseFile class that inherits from DispenseEntity that implements IDispenseEntity use a custom equality for purposes of comparing elements in List.
My interface is:
public interface IDispenseEntity : IEquatable<IDispenseEntity>
{
byte[] Id { get; set; }
List<byte[]> Key { get; }
List<byte[]> ParentKey { get; set; }
double Volume { get; set; }
public bool DispenseEnabled { get; set; }
string Name { get; set; }
}
My DispenseEntity class:
public class DispenseEntity : IDispenseEntity, IEquatable<IDispenseEntity>
{
//Other properties and methods
//I've tried - this implements IEquatable:
public bool Equals(IDispenseEntity other)
{
return Id.SequenceEqual(other.Id);
}
//I've tried - this overrides default:
public override bool Equals(object obj)
{
return Id.SequenceEqual((obj as IDispenseEntity).Id);
}
}
My DispenseFile class:
public class DispenseFile : DispenseEntity, IParent, IOutputable, IDispenseFile
{
//Other methods and properties
public override bool Equals(object obj)
{
return base.Equals(obj);
}
}
No matter what I use in DispenseEntity class for Equals() method it does not get used when I try:
List<IDispenseEntity> before = _aList;
List<IDispenseEntity> after = _bList;
var intersect = before.Intersect(after).ToList();
The intersect list has zero elements.
I am absolutely positive that both _aList and _bList have an instance of DispenseFile that inherits DispenseEntity who implements IDispenseEntity. I have written test code that finds the only DispenseFile entity in _aList and finds a single instance of DispenseFile in _bList. Both of these instances are created separately and have identical property Id ( new byte[] {1,2,3,4} )
I have tried overriding Equals. I have tried adding IEquatable to the base class and implementing the equals and GetHashCode and those don't get used.
The problem has to be me, what am I doing wrong?
You don't even need to implement IEquatable anything; overriding GetHashCode and Equals will suffice
public class DispenseEntity : IDispenseEntity
{
...
public override int GetHashCode()
{
return new BigInteger(Id).GetHashCode(); //or your choice
}
public override bool Equals(object obj)
{
return obj is DispenseEntity x && x.Id.SequenceEqual(Id);
}
}
No matter what I use in DispenseEntity class for Equals() method it does not get used
Indeed it might not; a précis on Intersect:
Intersect uses a HashSet; the contents of list B are added to a set, then the contents of A are enumerated and attempted to be added to the set. If the Add returns false, indicating the item is already known, the item from A is yielded. At the end of the operation all those items from A that are also in B have been returned
Now, for a HashSet using the default hashcode provider to decide if it contains some object X it first gets X's hashcode and looks in the objects it knows about to see if there are any other objects with the same hashcode.
If there are no known objects with the same hash, then it deems that the set doesn't contain the object.
If there are object(s) with the same hash, then it uses Equals to decide if the colliding object truly is the same. If you rely on the default hashcode implementation from object it's essentially the memory address of the item, so the only way you'd get the same hashcode is if list A and list B share an instance
Long story short, if you don't override GetHashCode you'll get an empty set result because when all of _bList is added to the set, and then all of _aList is enumerated and the set is asked "got this one?" the answer is always "no" - Equals never needs to be used to figure out if instances are the same because the hashes are always different, and the intersection is { } (nothing)
..but if you've got Equals and GetHashCode overridden, you should be good to go. You could even override GetHashCode to return 1 (don't; it would be terribly inefficient) and you'd see Equals used (a lot)..
I receive a certain category of products from a server. These products all have an ID. The relevant part is something like this:
public class Product
{
public Product(int id)
{
Id = id;
}
public int Id { get; private set; }
}
In the app itself, I am also using an ID of -1 to indicate a default/null option. There are several options like this, e.g. an ID of -2 for "use the same as parent". At first I checked this in the most primitive way.
if(product.Id == -1)
That is not really clean code, so I changed it to a bool property on the product:
public bool IsDefault
{
get { return Id == -1; }
}
Then I noticed that, as the whole class is immutable, this bool can just be an auto-property which can already be assigned on construction.
bool isDefault = id == -1;
Product product = new Product(id, isDefault);
Right at the moment I tried an approach of using an interface and having an own implementation for the default product, like this:
public interface IProduct
{
int Id{get;}
bool IsDefault{get;}
}
public class Product : IProduct
{
public Product(int id)
{
Id = id;
}
public int Id { get; private set; }
public bool IsDefault { get{ return false; } }
}
public class DefaultProduct : IProduct
{
public int Id { get{ return -1; }
public bool IsDefault { get{ return true; } }
}
Now, the last three examples (check in property / constructor parameter / interface implementations) all seem equally "clean" to me and I don't see any advantage one would have over the others.
Is there any good argument (apart from personal opinion) to prefer one over the others?
You don't need the Default version of a product.
You can create constructor without parameters to create default object.
You're overthinking it, and overcomplicating it with the interface.
I would set IsDefault in the constructor based on the passed Id parameter. And maybe even add a default value to that, so that you can instantiate the class without a parameter, and it should be a default product.
public class Product
{
public Product(int id = -1)
{
Id = id;
IsDefault = id == -1;
}
public int Id { get; private set; }
public bool IsDefault { get; private set; }
}
Although this is always easier said than done, the answer came with reevaluating basic assumptions about what certain things actually are and/or represent.
An ID X points to a product with the ID X. That is the purpose of that ID and using it for a different purpose is abusing it. So
if(product.Id == -1)
And
public bool IsDefault
{
get { return Id == -1; }
}
Are both semantically wrong. isDefault is a different part of the configuration of the class.
It being part of the configuration of the class is also the reason to dismiss the last option. A new class should encapsulate new behaviour. In order to achieve the same behaviour, just for a different configuration, the class should be created with just that, a different configuration.
Now, there is the tempting solution of putting IsDefault = id == -1; in the constructor. However, this would violate SRP. The one reason why the Product class should change is if what a product does changes. If tomorrow the ID for a default product changes to -32, there is no change in what the product does.
However, I have a class ProductParser where Products are created. The one reason why that class should change is if how products are parsed from the server to my app changes - and that is exactly the change we would have then.
So, according to clean code, there is only one correct approach here: Determine isDefault wherever it is created and pass it to the constructor of Product, which keeps it as an immutable value.
I've solved a problem I was having but although I've found out how something works (or doesn't) I'm not clear on why.
As I'm the type of person who likes to know the "why" I'm hoping someone can explain:
I have list of items and associated comments, and I wanted to differentiate between admin comments and user comments, so I tried the following code:
User commentUser = userRepository.GetUserById(comment.userId);
Role commentUserRole = context.Roles.Single(x=>x.Name == "admin");
if(commentUser.Roles.Contains(commentUserRole)
{
//do stuff
}
else
{
// do other stuff
}
Stepping through the code showed that although it had the correct Role object, it didn't recognise the role in the commentUser.Roles
The code that eventually worked is:
if(commentUser.Roles.Any(x=>x.Name == "admin"))
{
//do stuff
}
I'm happy with this because it's less code and in my opinion cleaner, but I don't understand how contains didn't work.
Hoping someone can clear that up for me.
This is probably because you didn't override the equality comparisons (Equals, GetHashCode, operator==) on your Role class. Therefore, it was doing reference comparison, which really isn't the best idea, as if they're not the same object, it makes it think it's a different. You need to override those equality operators to provide value equality.
You have to override Equals (and always also GetHashCode then) if you want to use Contains. Otherwise Equals will just compare references.
So for example:
public class Role
{
public string RoleName{ get; set; }
public int RoleID{ get; set; }
// ...
public override bool Equals(object obj)
{
Role r2 = obj as Role;
if (r2 == null) return false;
return RoleID == r2.RoleID;
}
public override int GetHashCode()
{
return RoleID;
}
public override string ToString()
{
return RoleName;
}
}
Another option is to implement a custom IEqualityComparer<Role> for the overload of Enumerable.Contains:
public class RoleComparer : IEqualityComparer<Role>
{
public bool Equals(Role x, Role y)
{
return x.RoleID.Equals(y.RoleID);
}
public int GetHashCode(Role obj)
{
return obj.RoleID;
}
}
Use it in this way:
var comparer = new RoleComparer();
User commentUser = userRepository.GetUserById(comment.userId);
Role commentUserRole = context.Roles.Single(x=>x.Name == "admin");
if(commentUser.Roles.Contains(commentUserRole, comparer))
{
// ...
}
When using the Contains-method, you check if the the array Roles of the user-object contains the object you have retrieved from the database beforehand. Though the array contains an object for the role "admin" it does not contain the exact object you fetched before.
When using the Any-method you check if there is any role having the name "admin" - and that delivers the expected result.
To get the same result with the Contains-method implement the IEquatable<Role>-interface on the role-class and compare the name to check whether two instances have actually the same value.
It will be your equality comparison for a Role.
The object in commentUserRole is not the same object as the one you are looking for commentUser.Roles.
Your context object will create a new object when you select from it and populate your Roles property with a collection of new Roles. If your context is not tracking the objects in order to return the same object when a second copy is requested then it will be a different object even though all the properties may be the same. Hence the failure of Contains
Your Any clause is explicitly checking the Name property which is why it works
Try making Role implement IEquatable<Role>
public class Role : IEquatable<Role> {
public bool Equals(Role compare) {
return compare != null && this.Name == compare.Name;
}
}
Whilst MSDN shows you only need this for a List<T> you may actually need to override Equals and GetHashCode to make this work
in which case:
public class Role : IEquatable<Role> {
public bool Equals(Role compare) {
return compare != null && this.Name == compare.Name;
}
public override bool Equals(object compare) {
return this.Equals(compare as Role); // this will call the above equals method
}
public override int GetHashCode() {
return this.Name == null ? 0 : this.Name.GetHashCode();
}
}
I'm storing items in a strongly typed IDictionary<TKey, TValue> such that the value also represents the key:
public class MyObject
{
public string Name { get; private set; }
public SectionId Section { get; private set; }
public MyObject(SectionId section, string name)
{
Section = section;
Name = name;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof(MyObject)) return false;
return Equals((MyObject)obj);
}
public override int GetHashCode()
{
unchecked
{
return (Name.ToLower().GetHashCode() * 397) ^ Section.GetHashCode();
}
}
}
In my presentation tier, I need to iterate through this Dictionary, adding each item to a ListBox control. I'm having a difficult time figuring out how to transform MyObject (which also acts as a key) into a string that the ListBox control can use as a value. Should I just make an explicit call to MyObject.GetHashCode() like this:
MyListBox.Add(new ListItem(myObject.Name, myObject.GetHashCode())
I would think of overriding the toString method and in here you will basically write code that will generate a meaningful string to be displayed in the ui
Hope I understood your question correctly.
Should I just make an explicit call to MyObject.GetHashCode()
No, GetHashCode() is:
Not guaranteed to give you a unique value, and
Going to be very difficult to reverse-engineer to produce a MyObject from.
Instead, each of your MyObjects should have some kind of unique identifier key. This can be an enum value or a number generated by an IDENTITY column in your database, or just a string that uniquely identifies each particular MyObject, and from which you can retrieve the MyObject from whatever collection or database you're using as a repository.
If there can only ever be a single MyObject with a given Name and Section, you could just combine the two: SectionId + ":" + Name. That way you can parse those two values out after the fact.