Benefits of using IEquatable - c#

I've been researching IEqualityComparer and IEquitable.
From posts such as What is the difference between IEqualityComparer<T> and IEquatable<T>? the difference between the two is now clear. "IEqualityComparer is an interface for an object that performs the comparison on two objects of the type T."
Following the example at https://msdn.microsoft.com/en-us/library/ms132151(v=vs.110).aspx the purpose of IEqualityComparer is clear and simple.
I've followed the example at https://dotnetcodr.com/2015/05/05/implementing-the-iequatable-of-t-interface-for-object-equality-with-c-net/ to work out how to use it and I get the following code:
class clsIEquitable
{
public static void mainLaunch()
{
Person personOne = new Person() { Age = 6, Name = "Eva", Id = 1 };
Person personTwo = new Person() { Age = 7, Name = "Eva", Id = 1 };
//If Person didn't inherit from IEquatable, equals would point to different points in memory.
//This means this would be false as both objects are stored in different locations
//By using IEquatable on class it compares the objects directly
bool p = personOne.Equals(personTwo);
bool o = personOne.Id == personTwo.Id;
//Here is trying to compare and Object type with Person type and would return false.
//To ensure this works we added an overrides on the object equals method and it now works
object personThree = new Person() { Age = 7, Name = "Eva", Id = 1 };
bool p2 = personOne.Equals(personThree);
Console.WriteLine("Equatable Check", p.ToString());
}
}
public class Person : IEquatable<Person>
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public bool Equals(Person other)
{
if (other == null) return false;
return Id == other.Id;
}
//These are to support creating an object and comparing it to person rather than comparing person to person
public override bool Equals(object obj)
{
if (obj is Person)
{
Person p = (Person)obj;
return Equals(p);
}
return false;
}
public override int GetHashCode()
{
return Id;
}
}
My question is WHY would I use it? It seems like a lot of extra code to the simple version below (bool o):
//By using IEquatable on class it compares the objects directly
bool p = personOne.Equals(personTwo);
bool o = personOne.Id == personTwo.Id;

IEquatable<T> is used by generic collections to determine equality.
From this msdn article https://msdn.microsoft.com/en-us/library/ms131187.aspx
The IEquatable interface is used by generic collection objects such as Dictionary, List, and LinkedList when testing for equality in such methods as Contains, IndexOf, LastIndexOf, and Remove. It should be implemented for any object that might be stored in a generic collection.
This provides an added benefit when using structs, since calling the IEquatable<T> equals method does not box the struct like calling the base object equals method would.

Related

Value-equals and circular references: how to resolve infinite recursion?

I have some classes that contain several fields. I need to compare them by value, i.e. two instances of a class are equal if their fields contain the same data. I have overridden the GetHashCode and Equals methods for that.
It can happen that these classes contain circular references.
Example: We want to model institutions (like government, sports clubs, whatever). An institution has a name. A Club is an institution that has a name and a list of members. Each member is a Person that has a name and a favourite institution. If a member of a certain club has this club as his favourite institution, we have a circular reference.
But circular references, in conjunction with value equality, lead to infinite recursion. Here is a code example:
interface IInstitution { string Name { get; } }
class Club : IInstitution
{
public string Name { get; set; }
public HashSet<Person> Members { get; set; }
public override int GetHashCode() { return Name.GetHashCode() + Members.Count; }
public override bool Equals(object obj)
{
Club other = obj as Club;
if (other == null)
return false;
return Name.Equals(other.Name) && Members.SetEquals(other.Members);
}
}
class Person
{
public string Name { get; set; }
public IInstitution FavouriteInstitution { get; set; }
public override int GetHashCode() { return Name.GetHashCode(); }
public override bool Equals(object obj)
{
Person other = obj as Person;
if (other == null)
return false;
return Name.Equals(other.Name)
&& FavouriteInstitution.Equals(other.FavouriteInstitution);
}
}
class Program
{
public static void Main()
{
Club c1 = new Club { Name = "myClub", Members = new HashSet<Person>() };
Person p1 = new Person { Name = "Johnny", FavouriteInstitution = c1 }
c1.Members.Add(p1);
Club c2 = new Club { Name = "myClub", Members = new HashSet<Person>() };
Person p2 = new Person { Name = "Johnny", FavouriteInstitution = c2 }
c2.Members.Add(p2);
bool c1_and_c2_equal = c1.Equals(c2); // StackOverflowException!
// c1.Equals(c2) calls Members.SetEquals(other.Members)
// Members.SetEquals(other.Members) calls p1.Equals(p2)
// p1.Equals(p2) calls c1.Equals(c2)
}
}
c1_and_c2_equal should return true, and in fact we (humans) can see that they are value-equal with a little bit of thinking, without running into infinite recursion. However, I can't really say how we figure that out. But since it is possible, I hope that there is a way to resolve this problem in code as well!
So the question is: How can I check for value equality without running into infinite recursions?
Note that I need to resolve circular references in general, not only the case from above. I'll call it a 2-circle since c1 references p1, and p1 references c1. There can be other n-circles, e.g. if a club A has a member M whose favourite is club B which has member N whose favourite club is A. That would be a 4-circle. Other object models might also allow n-circles with odd numbers n. I am looking for a way to resolve all these problems at once, since I won't know in advance which value n can have.
An easy workaround (used in RDBMS) is to use a unique Id to identify a Person(any type). Then you don't need to compare every other property and you never run into such cuircular references.
Another way is to compare differently in Equals, so provide the deep check only for the type of the Equals and not for the referenced types. You could use a custom comparer:
public class PersonNameComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (x == null && y == null) return true;
if (x == null || y == null) return false;
if(object.ReferenceEquals(x, y)) return true;
return x.Name == y.Name;
}
public int GetHashCode(Person obj)
{
return obj?.Name?.GetHashCode() ?? int.MinValue;
}
}
Now you can change the Equals implementation of Club to avoid that the Members(Persons) will use their deep check which includes the institution but only their Name:
public override bool Equals(object obj)
{
if (Object.ReferenceEquals(this, obj))
return true;
Club other = obj as Club;
if (other == null)
return false;
var personNameComparer = new PersonNameComparer();
return Name.Equals(other.Name)
&& Members.Count == other.Members.Count
&& !Members.Except(other.Members, personNameComparer).Any();
}
You notice that i can't use SetEquals because there is no overload for my custom comparer.
Following the suggestion of Dryadwoods, I changed the Equals methods so that I can keep track of the items that were already compared.
First we need an equality comparer that checks reference equality for corresponding elements of pairs:
public class ValuePairRefEqualityComparer<T> : IEqualityComparer<(T,T)> where T : class
{
public static ValuePairRefEqualityComparer<T> Instance
= new ValuePairRefEqualityComparer<T>();
private ValuePairRefEqualityComparer() { }
public bool Equals((T,T) x, (T,T) y)
{
return ReferenceEquals(x.Item1, y.Item1)
&& ReferenceEquals(x.Item2, y.Item2);
}
public int GetHashCode((T,T) obj)
{
return RuntimeHelpers.GetHashCode(obj.Item1)
+ 2 * RuntimeHelpers.GetHashCode(obj.Item2);
}
}
And here is the modified Equals method of Club:
static HashSet<(Club,Club)> checkedPairs
= new HashSet<(Club,Club)>(ValuePairRefEqualityComparer<Club>.Instance);
public override bool Equals(object obj)
{
Club other = obj as Club;
if (other == null)
return false;
if (!Name.Equals(other.Name))
return;
if (checkedPairs.Contains((this,other)) || checkedPairs.Contains((other,this)))
return true;
checkedPairs.Add((this,other));
bool membersEqual = Members.SetEquals(other.Members);
checkedPairs.Clear();
return membersEqual;
}
The version for Person is analogous. Note that I add (this,other) to checkedPairs and check if either (this,other) or (other,this) is contained because it might happen that after the first call of c1.Equals(c2), we end up with a call of c2.Equals(c1) instead of c1.Equals(c2). I am not sure if this actually happens, but since I can't see the implementation of SetEquals, I believe it is a possibility.
Since I am not happy with using a static field for the already checked pairs (it will not work if the program is concurrent!), I asked another question: make a variable last for a call stack.
For the general case that I am interested in
-- where we have classes C1, ..., Cn where each of these classes can have any number of VALUES (like int, string, ...) as well as any number of REFERENCES to any other classes of C1, ..., Cn (e.g. by having for each type Ci a field ICollection<Ci>) --
the question "Are two objects A and B equal?", in the sense of equality that I described here,
seems to be EQUIVALENT to
the question "For two finite, directed, connected, colored graphs G and H, does there exist an isomorphism from G to H?".
Here is the equivalence:
graph vertices correspond to objects (class instances)
graph edges correspond to references to objects
color corresponds to the conglomerate of values and the type itself (i.e. colors of two vertices are the same if their corresponding objects have the same type and the same values)
That's an NP-hard question, so I think I'm going to discard my plan to implement this and go with a circular-reference-free approach instead.

Loop through two collections to compare for identical collections in C#

I have two collections and I want to loop through each element and compare the corresponding elements in each collection for equality, thus determining if the collections are identical.
Is this possible with a foreach loop or must I use a counter and access the elements by index?
Generally speaking is there a preferred method for comparing collections for equality, like overloading an operator?
TIA.
You can use .SequenceEqual method which is used for this purpose. Read More.
Examples below if link is down or removed for some reason.
Determines whether two sequences are equal by comparing the elements
by using the default equality comparer for their type.
The SequenceEqual(IEnumerable, IEnumerable)
method enumerates the two source sequences in parallel and compares
corresponding elements by using the default equality comparer for
TSource, Default. The default equality comparer, Default, is used to
compare values of the types that implement the IEqualityComparer
generic interface. To compare a custom data type, you need to
implement this interface and provide your own GetHashCode and Equals
methods for the type.
class Pet
{
public string Name { get; set; }
public int Age { get; set; }
}
public static void SequenceEqualEx1()
{
Pet pet1 = new Pet { Name = "Turbo", Age = 2 };
Pet pet2 = new Pet { Name = "Peanut", Age = 8 };
// Create two lists of pets.
List<Pet> pets1 = new List<Pet> { pet1, pet2 };
List<Pet> pets2 = new List<Pet> { pet1, pet2 };
bool equal = pets1.SequenceEqual(pets2);
Console.WriteLine(
"The lists {0} equal.",
equal ? "are" : "are not");
}
/*
This code produces the following output:
The lists are equal.
*/
If you want to compare the actual data of the objects in the sequences
instead of just comparing their references, you have to implement the
IEqualityComparer generic interface in your class. The following
code example shows how to implement this interface in a custom data
type and provide GetHashCode and Equals methods.
public class Product : IEquatable<Product>
{
public string Name { get; set; }
public int Code { get; set; }
public bool Equals(Product other)
{
//Check whether the compared object is null.
if (Object.ReferenceEquals(other, null)) return false;
//Check whether the compared object references the same data.
if (Object.ReferenceEquals(this, other)) return true;
//Check whether the products' properties are equal.
return Code.Equals(other.Code) && Name.Equals(other.Name);
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public override int GetHashCode()
{
//Get hash code for the Name field if it is not null.
int hashProductName = Name == null ? 0 : Name.GetHashCode();
//Get hash code for the Code field.
int hashProductCode = Code.GetHashCode();
//Calculate the hash code for the product.
return hashProductName ^ hashProductCode;
}
}
Usage:
Product[] storeA = { new Product { Name = "apple", Code = 9 },
new Product { Name = "orange", Code = 4 } };
Product[] storeB = { new Product { Name = "apple", Code = 9 },
new Product { Name = "orange", Code = 4 } };
bool equalAB = storeA.SequenceEqual(storeB);
Console.WriteLine("Equal? " + equalAB);
/*
This code produces the following output:
Equal? True
*/

Why am I getting two different hashCodes?

This is my code. Why is the hashcode of my two objects not the same?
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
static void Main(string[] args)
{
Person p1 = new Person()
{
Age = 21,
Name = "Anna"
};
Person p2 = new Person()
{
Age = 21,
Name = "Anna"
};
int i = p1.GetHashCode();
int j = p2.GetHashCode();
Console.ReadLine();
}
Since you're not overriding GetHashCode, you'll be using the System.Object version by inheritance.
The GetHashCode method can be overridden by a derived type. If GetHashCode is not overridden, hash codes for reference types are computed by calling the Object.GetHashCode method of the base class, which computes a hash code based on an object's reference
In other words, since they're not the same object instance, they won't have the same hash code.
To make them have the same hash code, you'll have to implement your own GetHashCode by overriding it in the Person class, for example the simple;
public override int GetHashCode ()
{
return Name.GetHashCode () ^ Age.GetHashCode ();
}
Because they are not the same object, just holding the same data.

Comparing two lists and ignoring a specific property

I have two employee lists that I want to get only unique records from but this has a twist to it. Each list has an Employee class in it:
public class Employee
{
// I want to completely ignore ID in the comparison
public int ID{ get; set; }
// I want to use FirstName and LastName in comparison
public string FirstName{ get; set; }
public string LastName{ get; set; }
}
The only properties I want to compare on for a match are FirstName and LastName. I want to completely ignore ID in the comparison. The allFulltimeEmployees list has 3 employees in it and the allParttimeEmployees list has 3 employees in it. The first name and last name match on two items in the lists - Sally Jones and Fred Jackson. There is one item in the list that does not match because FirstName is the same, but LastName differs:
emp.id = null; // not populated or used in comparison
emp.FirstName = "Joe"; // same
emp.LastName = "Smith"; // different
allFulltimeEmployees.Add(emp);
emp.id = 3; // not used in comparison
emp.FirstName = "Joe"; // a match
emp.LastName = "Williams"; // not a match - different last name
allParttimeEmployees.Add(emp);
So I want to ignore the ID property in the class during the comparison of the two lists. I want to flag Joe Williams as a non-match since the last names of Smith and Williams in the two lists do not match.
// finalResult should only have Joe Williams in it
var finalResult = allFulltimeEmployees.Except(allParttimeEmployees);
I've tried using an IEqualityComparer but it doesn't work since it is using a single Employee class in the parameters rather than an IEnumerable list:
public class EmployeeEqualityComparer : IEqualityComparer<Employee>
{
public bool Equals(Employee x, Employee y)
{
if (x.FirstName == y.FirstName && x.LastName == y.LastName)
{
return true;
}
else
{
return false;
}
}
public int GetHashCode(Employee obj)
{
return obj.GetHashCode();
}
}
How can I successfully do what I want and perform this operation? Thanks for any help!
Your idea of using the IEqualityComparer is fine, it's your execution that is wrong. Notably, your GetHashCode method.
public int GetHashCode(Employee obj)
{
return obj.GetHashCode();
}
IEqualityComparer defines both Equals and GetHashCode because both are important. Do not ignore GetHashCode when you implement this interface! It plays a pivotal role on equality comparisons. No, it is not an indication that two items are equal, but it is an indicator that two elements are not. Two equal elements must return the same hash code. If they do not, they cannot be considered equal. If they do, then they might be equal, and equality functions only then go on to explore Equals.
With your implementation delegating to the GetHashCode method of the actual employee object, you are relying upon the implementation that Employee class uses. Only if that implementation is overriden will it be useful for you, and only if it is using your key fields. And if it is, then it is very likely that you did not need to define your own external comparer in the first place!
Build a GetHashCode method that factors in your key fields and you will be set.
public int GetHashCode(Employee obj)
{
// null handling omitted for brevity, but you will want to
// handle null values appropriately
return obj.FirstName.GetHashCode() * 117
+ obj.LastName.GetHashCode();
}
Once you have this method in place, then use the comparer in your call to Except.
var comparer = new EmployeeEqualityComparer();
var results = allFulltimeEmployees.Except(allParttimeEmployees, comparer);
You can override Equals and GetHashCode in your Employees class.
For example,
public class Employee
{
// I want to completely ignore ID in the comparison
public int ID { get; set; }
// I want to use FirstName and LastName in comparison
public string FirstName { get; set; }
public string LastName { get; set; }
public override bool Equals(object obj)
{
var other = obj as Employee;
return this.FirstName == other.FirstName && this.LastName == other.LastName;
}
public override int GetHashCode()
{
return this.FirstName.GetHashCode() ^ this.LastName.GetHashCode();
}
}
I tested with the following data set:
var empList1 = new List<Employee>
{
new Employee{ID = 1, FirstName = "D", LastName = "M"},
new Employee{ID = 2, FirstName = "Foo", LastName = "Bar"}
};
var empList2 = new List<Employee>
{
new Employee { ID = 2, FirstName = "D", LastName = "M" },
new Employee { ID = 1, FirstName = "Foo", LastName = "Baz" }
};
var result = empList1.Except(empList2); // Contained "Foo Bar", ID #2.
your IEqualityComparer should work:
var finalResult = allFulltimeEmployees.Except(allParttimeEmployees, new EmployeeEqualityComparer());
Try implementing the IEquatable(T) interface for your Employee class. You simply need to provide an implementation for an Equals() method, which you can define however you want (i.e. ignoring employee IDs).
The IEquatable interface is used by generic collection objects such
as Dictionary, List, and LinkedList when testing
for equality in such methods as Contains, IndexOf, LastIndexOf, and
Remove. It should be implemented for any object that might be stored
in a generic collection.
Example implementation of the Equals() method:
public bool Equals(Employee other)
{
return (other != null) && (FirstName == other.FirstName) && (LastName == other.LastName);
}
It's not the most elegant solution, but you could make a function like so
public string GetKey(Employee emp)
{
return string.Format("{0}#{1}", emp.FirstName, emp.LastName)
}
and then populate everything in allFullTimeEmployees into a Dictionary<string, Employee> where the key of the dictionary is the result of calling GetKey on each employee object. Then you could loop over allParttimeEmployees and call GetKey on each of those, probing into the dictionary (e.g. using TryGetValue or ContainsKey), and taking whatever action was necessary on a duplicate, such as removing the duplicate from the dictionary.

How does HashSet compare elements for equality?

I have a class that is IComparable:
public class a : IComparable
{
public int Id { get; set; }
public string Name { get; set; }
public a(int id)
{
this.Id = id;
}
public int CompareTo(object obj)
{
return this.Id.CompareTo(((a)obj).Id);
}
}
When I add a list of object of this class to a hash set:
a a1 = new a(1);
a a2 = new a(2);
HashSet<a> ha = new HashSet<a>();
ha.add(a1);
ha.add(a2);
ha.add(a1);
Everything is fine and ha.count is 2, but:
a a1 = new a(1);
a a2 = new a(2);
HashSet<a> ha = new HashSet<a>();
ha.add(a1);
ha.add(a2);
ha.add(new a(1));
Now ha.count is 3.
Why doesn't HashSet respect a's CompareTo method.
Is HashSet the best way to have a list of unique objects?
It uses an IEqualityComparer<T> (EqualityComparer<T>.Default unless you specify a different one on construction).
When you add an element to the set, it will find the hash code using IEqualityComparer<T>.GetHashCode, and store both the hash code and the element (after checking whether the element is already in the set, of course).
To look an element up, it will first use the IEqualityComparer<T>.GetHashCode to find the hash code, then for all elements with the same hash code, it will use IEqualityComparer<T>.Equals to compare for actual equality.
That means you have two options:
Pass a custom IEqualityComparer<T> into the constructor. This is the best option if you can't modify the T itself, or if you want a non-default equality relation (e.g. "all users with a negative user ID are considered equal"). This is almost never implemented on the type itself (i.e. Foo doesn't implement IEqualityComparer<Foo>) but in a separate type which is only used for comparisons.
Implement equality in the type itself, by overriding GetHashCode and Equals(object). Ideally, implement IEquatable<T> in the type as well, particularly if it's a value type. These methods will be called by the default equality comparer.
Note how none of this is in terms of an ordered comparison - which makes sense, as there are certainly situations where you can easily specify equality but not a total ordering. This is all the same as Dictionary<TKey, TValue>, basically.
If you want a set which uses ordering instead of just equality comparisons, you should use SortedSet<T> from .NET 4 - which allows you to specify an IComparer<T> instead of an IEqualityComparer<T>. This will use IComparer<T>.Compare - which will delegate to IComparable<T>.CompareTo or IComparable.CompareTo if you're using Comparer<T>.Default.
Here's clarification on a part of the answer that's been left unsaid: The object type of your HashSet<T> doesn't have to implement IEqualityComparer<T> but instead just has to override Object.GetHashCode() and Object.Equals(Object obj).
Instead of this:
public class a : IEqualityComparer<a>
{
public int GetHashCode(a obj) { /* Implementation */ }
public bool Equals(a obj1, a obj2) { /* Implementation */ }
}
You do this:
public class a
{
public override int GetHashCode() { /* Implementation */ }
public override bool Equals(object obj) { /* Implementation */ }
}
It is subtle, but this tripped me up for the better part of a day trying to get HashSet to function the way it is intended. And like others have said, HashSet<a> will end up calling a.GetHashCode() and a.Equals(obj) as necessary when working with the set.
HashSet uses Equals and GetHashCode().
CompareTo is for ordered sets.
If you want unique objects, but you don't care about their iteration order, HashSet<T> is typically the best choice.
constructor HashSet receive object what implement IEqualityComparer for adding new object.
if you whant use method in HashSet you nead overrride Equals, GetHashCode
namespace HashSet
{
public class Employe
{
public Employe() {
}
public string Name { get; set; }
public override string ToString() {
return Name;
}
public override bool Equals(object obj) {
return this.Name.Equals(((Employe)obj).Name);
}
public override int GetHashCode() {
return this.Name.GetHashCode();
}
}
class EmployeComparer : IEqualityComparer<Employe>
{
public bool Equals(Employe x, Employe y)
{
return x.Name.Trim().ToLower().Equals(y.Name.Trim().ToLower());
}
public int GetHashCode(Employe obj)
{
return obj.Name.GetHashCode();
}
}
class Program
{
static void Main(string[] args)
{
HashSet<Employe> hashSet = new HashSet<Employe>(new EmployeComparer());
hashSet.Add(new Employe() { Name = "Nik" });
hashSet.Add(new Employe() { Name = "Rob" });
hashSet.Add(new Employe() { Name = "Joe" });
Display(hashSet);
hashSet.Add(new Employe() { Name = "Rob" });
Display(hashSet);
HashSet<Employe> hashSetB = new HashSet<Employe>(new EmployeComparer());
hashSetB.Add(new Employe() { Name = "Max" });
hashSetB.Add(new Employe() { Name = "Solomon" });
hashSetB.Add(new Employe() { Name = "Werter" });
hashSetB.Add(new Employe() { Name = "Rob" });
Display(hashSetB);
var union = hashSet.Union<Employe>(hashSetB).ToList();
Display(union);
var inter = hashSet.Intersect<Employe>(hashSetB).ToList();
Display(inter);
var except = hashSet.Except<Employe>(hashSetB).ToList();
Display(except);
Console.ReadKey();
}
static void Display(HashSet<Employe> hashSet)
{
if (hashSet.Count == 0)
{
Console.Write("Collection is Empty");
return;
}
foreach (var item in hashSet)
{
Console.Write("{0}, ", item);
}
Console.Write("\n");
}
static void Display(List<Employe> list)
{
if (list.Count == 0)
{
Console.WriteLine("Collection is Empty");
return;
}
foreach (var item in list)
{
Console.Write("{0}, ", item);
}
Console.Write("\n");
}
}
}
I came here looking for answers, but found that all the answers had too much info or not enough, so here is my answer...
Since you've created a custom class you need to implement GetHashCode and Equals. In this example I will use a class Student instead of a because it's easier to follow and doesn't violate any naming conventions. Here is what the implementations look like:
public override bool Equals(object obj)
{
return obj is Student student && Id == student.Id;
}
public override int GetHashCode()
{
return HashCode.Combine(Id);
}
I stumbled across this article from Microsoft that gives an incredibly easy way to implement these if you're using Visual Studio. In case it's helpful to anyone else, here are complete steps for using a custom data type in a HashSet using Visual Studio:
Given a class Student with 2 simple properties and an initializer
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public Student(int id)
{
this.Id = id;
}
}
To Implement IComparable, add : IComparable<Student> like so:
public class Student : IComparable<Student>
You will see a red squiggly appear with an error message saying your class doesn't implement IComparable. Click on suggestions or press Alt+Enter and use the suggestion to implement it.
You will see the method generated. You can then write your own implementation like below:
public int CompareTo(Student student)
{
return this.Id.CompareTo(student.Id);
}
In the above implementation only the Id property is compared, name is ignored. Next right-click in your code and select Quick actions and refactorings, then Generate Equals and GetHashCode
A window will pop up where you can select which properties to use for hashing and even implement IEquitable if you'd like:
Here is the generated code:
public class Student : IComparable<Student>, IEquatable<Student> {
...
public override bool Equals(object obj)
{
return Equals(obj as Student);
}
public bool Equals(Student other)
{
return other != null && Id == other.Id;
}
public override int GetHashCode()
{
return HashCode.Combine(Id);
}
}
Now if you try to add a duplicate item like shown below it will be skipped:
static void Main(string[] args)
{
Student s1 = new Student(1);
Student s2 = new Student(2);
HashSet<Student> hs = new HashSet<Student>();
hs.Add(s1);
hs.Add(s2);
hs.Add(new Student(1)); //will be skipped
hs.Add(new Student(3));
}
You can now use .Contains like so:
for (int i = 0; i <= 4; i++)
{
if (hs.Contains(new Student(i)))
{
Console.WriteLine($#"Set contains student with Id {i}");
}
else
{
Console.WriteLine($#"Set does NOT contain a student with Id {i}");
}
}
Output:

Categories