Implementing Linq Distinct in c# - c#

I have written the following code to implement Linq.Distinct(IEqualityComparer) in the most basic way possible, however simpleCollection returns 2 items instead if 1.
Oddly, ive noticed that breakpoints on Equals never get hit.
Could it be something to do with my implementation of GetHashCode()?
public class testobjx
{
public int i { get; set; }
}
public class mytest
{
public Main()
{
var simpleCollection = new[] { new testobjx() { i = 1 }, new testobjx() { i = 1 } }.Distinct(new DistinctCodeType());
var itemCount = simpleCollection.Count();//this should return 1 not 2.
}
}
public class DistinctCodeType : IEqualityComparer<testobjx>
{
public bool Equals(testobjx x, testobjx y)
{
return x.i == y.i;
}
public int GetHashCode(testobjx obj)
{
return obj.GetHashCode();
}
}

Try:
public int GetHashCode(testobjx obj)
{
if (obj == null) return 0;
return obj.i.GetHashCode();
}

The default implementation of GetHashCode for an object is based on the object's instance, so two instances of testobjx with the same value have different hash codes. You need to modify your GetHashCode method to interrogate the object's property. If the object has multiple properties you need to figure out which ones are required to uniquely identify the object and compose a single hash code from those.

Related

Why when i call the remove operation of this hashset it doesn't use the gethashcode neither the equality implementation?

i am doing an app to manage the creation of role-playing sessions, but i am having problems with the section to do rules summaries so the Master doesnt have to be reading the core book every sec, i have the data structures in this way.
User have a list of campaigns, that campaign have a list of scenaries and that scenaries have a list of adventures.
Users -> Lcampaings -> Lscenaries -> Ladventures
Each campaign, scenary or adventure, have resources which contains the list of documents, images, resources etc, and a hashset of summaries.
Campaign/Scenary/Adventure -> Resources -> Ldocuments/LImages/.../HashSet Summaries
ok, so to modify the summaries i have implemented equality and gethashcode
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Windows;
namespace ElEscribaDelDJ.Classes
{
public class Resumenes: INotifyPropertyChanged, IEqualityComparer<Resumenes>
{
private string _nombre;
public string Nombre
{
get { return _nombre; }
set {
_nombre = value;
OnPropertyChanged("Nombre");
}
}
private string _etiquetas;
public string Etiquetas
{
get { return _etiquetas; }
set {
_etiquetas = value;
OnPropertyChanged("Etiquetas");
}
}
private string _descripcion;
public string Descripcion
{
get { return _descripcion; }
set {
_descripcion = value;
OnPropertyChanged("Descripcion");
}
}
private int _pagina;
public int Pagina
{
get { return _pagina; }
set {
if (value <= 0)
_pagina = 1;
else
_pagina = value;
OnPropertyChanged("Pagina");
}
}
private string _manual;
public string Manual
{
get { return _manual; }
set {
_manual = value;
OnPropertyChanged("Manual");
}
}
private string _manualurl;
public string ManualUrl
{
get { return _manualurl; }
set
{
_manualurl = value;
OnPropertyChanged("ManualUrl");
}
}
private string _tipoaventura;
public string TipoAventura
{
get { return _tipoaventura; }
set {
_tipoaventura = value;
OnPropertyChanged("TipoAventura");
}
}
private string _nombretipoaventura;
public string NombreTipoAventura
{
get { return _nombretipoaventura; }
set {
_nombretipoaventura = value;
OnPropertyChanged("NombreTipoAventura");
}
}
private int _indice;
public int Indice
{
get { return _indice; }
set
{
_indice = value;
OnPropertyChanged("Indice");
}
}
private List<int> _indiceslibres;
public List<int> IndicesLibres
{
get { return _indiceslibres; }
set
{
_indiceslibres = value;
OnPropertyChanged("IndicesLibres");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
public bool Equals(Resumenes x, Resumenes y)
{
if (x.Nombre.Equals(y.Nombre) && x.Descripcion.Equals(y.Descripcion))
return true;
else
return false;
}
public int GetHashCode(Resumenes obj)
{
MessageBox.Show("El Hash code es " + obj.Nombre.GetHashCode());
return obj.Nombre.GetHashCode();
}
public Resumenes CopiarValores ()
{
return (Resumenes)this.MemberwiseClone();
}
}
}
(In gethashcode i have the messagebox just to know if was called ofc i know it shouldnt be there)
I am using the name and description of two objects to know if they are equally or not, and for gethashcode the name.
I have done this after read a lot of questions about how it works hashcode and equallity, hashcodeA == hashcodeB means they could be equally so name looks like perfect for that and thats why in equallity i use also description, because if you have same name and same description its mostly the same summary.
Ok, so i show a list of all summaries, the user select one, click edit, in the windows for add or edit i do a not in deep copy of the objects and after that i call for example campaign edit summary, where i delete the old object and add the new one, because i readed that's the best way if you have modified the fields used to make the hashcode.
public void EditarResumen(Resumenes resumenviejo, Resumenes resumennuevo)
{
DatosAplicacion.CampanaSeleccionada.Recursos.Resumenes.Remove(resumenviejo);
DatosAplicacion.CampanaSeleccionada.Recursos.Resumenes.Add(resumennuevo);
RecursosAplicacion.SesionUsuario.ReemplazarCampana();
}
"Datosaplicacion" is a static class which have the campaign, scenary and aventure that the users chose from all of them
using System;
using System.Collections.Generic;
using System.Text;
namespace ElEscribaDelDJ.Classes.Utilidades.Aplicacion
{
public static class DatosAplicacion
{
private static Campana _campana = new Campana();
public static Campana CampanaSeleccionada
{
get { return _campana; }
set { _campana = value; }
}
public static int IndiceCampana;
private static EscenarioCampana _escenarioseleccionado = new EscenarioCampana();
public static EscenarioCampana EscenarioSeleccionado
{
get { return _escenarioseleccionado; }
set { _escenarioseleccionado = value; }
}
public static int IndiceEscenario;
private static Aventura _aventuraseleccionada;
public static Aventura AventuraSeleccionada
{
get { return _aventuraseleccionada; }
set { _aventuraseleccionada = value; }
}
public static int IndiceAventuraSeleccionada;
}
}
resumenviejo (oldsummary) is made with
public Resumenes CopiarValores ()
{
return (Resumenes)this.MemberwiseClone();
}
this should be fine because i dont have any reference object or similar.
But when i debugg the application the remove operation always throw false, and never calls the equality operation neither the gethashcode.
And i don't know what is happening.
I used this article to do the operations https://dotnetcodr.com/2017/01/12/how-to-check-whether-two-hashsets-are-equal-in-c-net-2/#:~:text=Two%20HashSet%20objects%20in%20C#,their%20order%20in%20the%20collection.
I have the full code uploaded to github https://github.com/davidgmd/Proyecto-de-fin-de-grado
You have two methods GetHashCode and Equals
public bool Equals(Resumenes x, Resumenes y)
public int GetHashCode(Resumenes obj)
But they are not overriding the correct methods from the framework so they won't be called. You have to override the following to methods, so that they will be used by the framework
public override bool Equals(object obj) {
if (!(obj is Resumenes)) return false;
var other = obj as Resumenes;
return this.Nombre.Equals(other.Nombre) && this.Descripcion.Equals(other.Descripcion);
}
public override int GetHashCode() {
return this.Nombre.GetHashCode();
}
Note, that this is not really needed. It's just to clarify that this instance is compared with the other object passed in.
EDIT
You can use your overriding of IEqualityComparer<Resumenes> but then you will have to pass it to the constructor of the hashset. But it's quite uncommon for the data object you put into a HashSet to implement IEqualityComparer. Better your Resumenes should implement the IEquatable<T> interface
public class Resumenes: INotifyPropertyChanged, IEquatable<Resumenes> {
public override bool Equals(object obj) { ... }
public bool Equals(Resumenes other) { ... }
public override int GetHashCode() { ... }
}
There are a few things here:
since Nombre is effectively the hash-key, if it changes while the item is in the hash: all bets are off; a simple way to avoid that is to make it read-only
it is very odd to have a leaf type implement IEqualityComparer<T>; I wonder if this is a large part of the problem - especially if you haven't passed a explicit comparer into the hash-set; however, honestly, it would be simpler and preferable to implement IEquatable<T> here:
public class Resumenes : INotifyPropertyChanged, IEquatable<Resumenes>
{
// ...
public override bool Equals(object obj) => obj is Resumenes other && Equals(other);
public bool Equals(Resumenes other)
=> other is not null && other.Nombre == this.Nombre && other.Descripcion == this.Descripcion;
public override int GetHashCode()
=> Nombre.GetHashCode();
}
You can do this with a custom equality comparer, but you'd need to explicitly pass such a comparer into the new HashSet<Resumenes>(comparer) constructor. I would expect this comparer to be a singleton instance of a different type, for example ResumenesComparer.Instance. Using IEquatable<T> is far more obvious and convenient.

HashSet sort by property but set unicity with another

I would like to know how can with a SortedSet, I can Sort by one property of my object and in the other hand set the unicity to another property.
Here what I have :
using System;
using System.Collections.Generic;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var bonusSortedSet = new SortedSet<Bonus>(new ComparerByNumber());
var bonus0 = new Bonus()
{
ID = "6479cc32-960d-4aa0-a62d-8c81e65085e8",
Number = 15
};
var bonus1 = new Bonus()
{
ID = "8e8a9c1b-1889-4c4c-b039-b1dbe005719b",
Number = 10
};
var bonus2 = new Bonus()
{
ID = "3453f78d-ce28-4ab1-a7a1-395063374f87",
Number = 11
};
var bonus3 = new Bonus()
{
ID = "8e8a9c1b-1889-4c4c-b039-b1dbe005719b",
Number = 12
};
bonusSortedSet.Add(bonus0);
bonusSortedSet.Add(bonus1);
bonusSortedSet.Add(bonus2);
bonusSortedSet.Add(bonus3);
foreach (var bonus in bonusSortedSet)
{
Console.WriteLine($"{bonus.ID} : {bonus.Number}");
}
}
}
public class Bonus : IEqualityComparer<Bonus>
{
public string ID { get; set; }
public int Number { get; set; }
public bool Equals(Bonus x, Bonus y)
{
return x.GetHashCode() == y.GetHashCode();
}
public int GetHashCode(Bonus obj)
{
return obj != null ? obj.ID.GetHashCode() : string.Empty.GetHashCode();
}
}
public class ComparerByNumber : IComparer<Bonus>
{
public int Compare(Bonus x, Bonus y)
{
return Math.Sign(x.Number - y.Number);
}
}
}
The result is :
8e8a9c1b-1889-4c4c-b039-b1dbe005719b : 10
3453f78d-ce28-4ab1-a7a1-395063374f87 : 11
8e8a9c1b-1889-4c4c-b039-b1dbe005719b : 12
6479cc32-960d-4aa0-a62d-8c81e65085e8 : 15
I would have expect :
8e8a9c1b-1889-4c4c-b039-b1dbe005719b : 10
3453f78d-ce28-4ab1-a7a1-395063374f87 : 11
6479cc32-960d-4aa0-a62d-8c81e65085e8 : 15
You haven't implemented any code to make the Bonus objects unique by their ID property, only by their Number property. Even if you did, such as:
public int Compare(Bonus x, Bonus y)
{
if (x.ID == y.ID) return 0;
return Math.Sign(x.Number - y.Number);
}
This can work in some situations, but is completely driven by the internal implementation of SortedSet<T> and is unlikely to work 99% of the time.
You can't have the set sorted by one property and the uniqueness dictated by a different one. If you want to track order and uniqueness on two independent properties, you'll need two collection objects.
Your class implements IEqualityComparer<Bonus> which is wrong. You do not want a bonus to be a comparer, you want a bonus to be equatable.
Also, do not implement Equals by checking if hash codes are identical. Hash collisions will happen (but will be infrequent with a good GetHashCode).
Try writing the Bonus type like this:
public sealed class Bonus
{
public string ID { get; set; }
public int Number { get; set; }
public override bool Equals(object obj)
{
if (obj is Bonus other)
{
return ID == other.ID;
}
return false;
}
public override int GetHashCode()
{
return ID?.GetHashCode() ?? 0;
}
}
Then a HashSet<> with the implicit (default) equality comparer:
var bonusSet = new HashSet<Bonus>();
will ensure the set members are unique by ID. Then you can use Linq to sort the members when you enumerate them:
foreach (var bonus in bonusSet.OrderBy(x => x.Number))
{
Console.WriteLine($"{bonus.ID} : {bonus.Number}");
}

How to check if an object already exists in list of objects using C#

I would like to add classStudents to the list _ClassStudents only if classStudents is not yet in the list.
public class ClassStudents{
public Id {get; set;}
public Name {get; set;}
public List<Student> Student {get; set;}
}
public static List<ClassStudents> _ClassStudentsList = new List<ClassStudents>();
public static void addClassStudents(ClassStudents classStudents)
{
//if( classStudents isn't in ClassStudentsList ) <------
_ClassStudentsList.Add(classStudents);
}
How can I do this?
You can use the Any extension method:
var text = nameClassStudents.Text;
if(!_ClassStudentsList.Any(t => t.Text == text))
{
_ClassStudentsList.Add(new ClassStudents(text,(int)numericUpDown1.Value));
}
However, this does not guarantee that names will be unique in the list.
If you want it guaranteed, you can simply use a Dictionary<string, int> instead of a List<ClassStudents>.
The Dictionary will not allow duplicate keys.
Of course, here you would also want to check if the key exists before adding a value to the dictionary:
var dict = new Dictionary<string, int>;
...
var text = nameClassStudents.Text;
if(!dict.ContainsKey(text))
(
dict.Add(text, (int)numericUpDown1.Value);
)
You can also use a HashSet:
public class ClassStudents {
public Id {get; set;}
public Name {get; set;}
public override bool Equals(object obj) {
return this.Name.Trim().ToLower().Equals(((ClassStudents)obj).Name.Trim().ToLower());
}
public override int GetHashCode() {
return this.Name.GetHashCode();
}
}
In your main(), you can declare a HashSet like below:
HashSet <ClassStudents> hash = new HashSet<ClassStudents>();
Now, this will add only unique elements in the set.
I think the proper way is to tell your program what it means for two Turma types to be equal.
public class ClassStudents
{
public string Name { get; set; }
public int Value { get; set; }
// Override the equality operation to check for text value only
public override bool Equals(object obj)
{
if (obj is ClassStudents other) // use pattern matching
{
return Name.Equals(other.Name);
}
return false;
}
}
Now you can use the list method .Contains() for checking if item exists.
{
List<ClassStudents> classStudents = new List<ClassStudents>();
public Test()
{
// Add three values
classStudents.Add(new ClassStudents() { Name="ABC", Value=101 });
classStudents.Add(new ClassStudents() { Name="IJK", Value=111 });
classStudents.Add(new ClassStudents() { Name="XYZ", Value=101 });
// Check if exists before adding this value
ClassStudents x = new ClassStudents() { Name="ABC", Value=121 };
// `Contains()` calls the `Equals()` method of `classStudents`
if (!classStudents.Contains(x))
{
classStudents.Add(x);
}
}
}
This results in the cleanest code because it is obvious what the check if(!classStudents.Contains(x)) does. Also you are leveraging the power of the CLR by adding intelligence to your types. OOP is all about adding methods in your user classes to define actions. In this case, I am definition what equality means for this type and now the CLR can use this information where needed.

What is proper way to check if List<MyClass> contain copy of some MyClass object?

I have pretty complex structure:
public static List<state> states = new List<state>();
public class state : List<situation> { }
public class situation {
//public rule rule; //another complex object
public int pos;
public string term;
public situation(/*rule rule,*/ string terminal, int pointPosition) {
//this.rule = rule;
this.term = terminal;
this.pos = pointPosition;
}
}
In my program i generate new state objects what must be added to states list. But only if here is no same state in this list (order of situation objects in state list is don't matter and can be different in two state what is equal in fact).
I tryed this:
states.Add(new state());
states[0].Add(new situation("#", 0));
state s = new state();
s.Add(new situation("#", 0));
if (states.Contains(s)) {
Console.WriteLine("HODOR"); //not performed
}
Looks like Contains don't work right with custom objects, so i must create some custom method.
I can just compare each objects and each fields but... it's look like pretty tedious and ugly solution. May be here is some better way to do this?
Override Equals in your situation class and implement your own equality i.e :
public class situation
{
public string Terminal
{
get{ return term;}
}
public int Pos
{
get{ return pos;}
}
public override bool Equals(object obj)
{
bool result;
situation s = obj as situation;
if (s != null)
{
result = Terminal.Equals(s.Terminal) && Pos == s.Pos;
}
return result;
}
}
I'm also added this:
public class state : List<situation> {
public override bool Equals(object obj) {
state s = obj as state;
if (s != null) {
foreach (situation situation in s) {
if (!this.Contains(situation)) { return false; }
}
foreach (situation situation in this) {
if (!s.Contains(situation)) { return false; }
}
return true;
}
return false;
}
}
So my example works.

Selecting Items using a HashSet C#

I have a HashSet. Is there a method that can utilize the IEqualityComparer for retrieving items where you pass in an object that will satisfies the equals method defined in the IEqualityComparer?
This might explain it a bit more.
public class Program
{
public static void Main()
{
HashSet<Class1> set = new HashSet<Class1>(new Class1Comparer());
set.Add( new Class1() { MyProperty1PK = 1, MyProperty2 = 1});
set.Add( new Class1() { MyProperty1PK = 2, MyProperty2 = 2});
if (set.Contains(new Class1() { MyProperty1PK = 1 }))
Console.WriteLine("Contains the object");
//is there a better way of doing this, using the comparer?
// it clearly needs to use the comparer to determine if it's in the hash set.
Class1 variable = set.Where(e => e.MyProperty1PK == 1).FirstOrDefault();
if(variable != null)
Console.WriteLine("Contains the object");
}
}
class Class1
{
public int MyProperty1PK { get; set; }
public int MyProperty2 { get; set; }
}
class Class1Comparer : IEqualityComparer<Class1>
{
public bool Equals(Class1 x, Class1 y)
{
return x.MyProperty1PK == y.MyProperty1PK;
}
public int GetHashCode(Class1 obj)
{
return obj.MyProperty1PK;
}
}
If you want to retrieve items based on a single property, you might want to use a Dictionary<T,U> instead of a hashset. You can then place the items within the dictionary, using MyProperty1PK as the key.
Your query then becomes simple:
Class1 variable;
if (!dictionary.TryGetValue(1, out variable)
{
// class wasn't in dictionary
}
Given that you're already storing using a comparer which only uses this value as the uniqueness criteria, there is really no disadvantage to just using that property as the key in a dictionary instead.

Categories