How can I use a HashSet<MyCustomClass> to remove duplicates of MyCustomClass? - c#

I have a HashSet<MyCustomClass> mySet = new HashSet<MyCustomClass>(); and I wish to remove all MyCustomClass's that contain the same values.
Let's say MyCustomClass looks like this:
public class MyCustomClass
{
Point point;
public MyCustomClass(int x, int y)
{
point.X = x;
point.Y = y;
}
// Other methods...
}
I tried to implement IEqualityComparer like MSDN suggests, and pass it through the constructor of the HashSet<MyCustomClass>(); but I ended up unsuccessfully.
What's the correct approach?
EDIT:
This is my Chain class and my ChainEqualityComparer:
public class Chain
{
HashSet<Mark> chain;
HashSet<Mark> marks;
public Chain(HashSet<Mark> marks)
{
chain = new HashSet<Mark>();
this.marks = marks;
}
// Other methods...
}
public class ChainEqualityComparer : IEqualityComparer<Chain>
{
#region IEqualityComparer<Chain> Members
public bool Equals(Chain x, Chain y)
{
if (x.ChainWithMarks.Count == y.ChainWithMarks.Count)
{
foreach (Mark mark in x.ChainWithMarks)
{
if (!y.ChainWithMarks.Contains(mark))
return false;
}
return true;
}
return false;
}
public int GetHashCode(Chain obj)
{
return obj.GetHashCode() ^ obj.GetType().GetHashCode();
}
#endregion
}
And this is my Mark class:
public class Mark
{
int x;
int y;
public Mark(int x, int y)
{
this.x = x;
this.y = y;
}
public int X
{
get { return x; }
set { x = value; }
}
public int Y
{
get { return y; }
set { y = value; }
}
}
public class MarkEqualityComparer : IEqualityComparer<Mark>
{
#region IEqualityComparer<Mark> Members
public bool Equals(Mark x, Mark y)
{
return (x.X == y.X) && (x.Y == y.Y);
}
public int GetHashCode(Mark obj)
{
return obj.GetHashCode() ^ obj.GetType().GetHashCode();
}
#endregion
}
(I can pastebin the code if it's too much code...)

You can use the EqualityComparer or just override Equals and GetHashCode.
You must make sure that whatever you consider to be a duplicate is identified as having an equivalent hash code, and returning true when tested for equality.
My guess is that you weren't returning equal hash codes. Could you post the code from your equality comparer?
As a test, you could do:
var set = new HashSet<MyCustomClass>();
var a = new MyCustomClass(1,2);
var b = new MyCustomClass(1,2);
set.Add(a);
set.Add(b);
Assert.IsTrue(a.Equals(b));
Assert.IsTrue(b.Equals(a));
Assert.AreEqual(a.GetHashCode(), b.GetHashCode());
Assert.AreEqual(1, set.Count);
A similar set of tests would be applicable to an equality comparer too.
EDIT
Yep, as suspected it's the hash code function. You need to calculate it based on the values of the type itself. A common enough mistake.
public int GetHashCode(Mark obj)
{
return ((MyCustomClass)obj).point.GetHashCode();
}
That assumes point is the only state field in your type.

I think you are getting tripped up becase two Mark instances with the same values won't be equal in your ChainEqualityComparer class. It doesn't appear like MarkEqualityComparer is ever used.
The line:
if (!y.ChainWithMarks.Contains(mark))
will always be false unless you override Equals and GetHashCode on the Mark class. (Except if you have two references to the same mark in both Chain x and Chain y, which I'm presuming is not what you want).
If y.ChainWithMarks is a HashSet and you want to use MarkEqualityComparer, make sure you create that HashSet with the correct constructor including an instance of MarkEqualityComparer.
Since Mark is a value type, you might consider using a struct to represent it, since the .Net runtime then uses value equality instead of referential equality when comparing. I think this is actually the most correct implementation of your idea.

Related

Overriding GetHash() And Equals()

I am having trouble overriding the GetHashCode() method and the Equals() method.
public class Coordinate
{
int x;
int y;
public Coordinate(int p,int q)
{
this.x = p ;
this.y = q;
}
}
Suppose I created two Coordinate point objects with same x and y coordinates .
I want my program to understand that they are equal.
Coordinate Point 1 = new Coordinate(0,0);
Coordinate Point 2 = new Coordinate(0,0);
By default they are giving different GetHashCode() as expected.
I want them to give same hash code by overriding it and then use that hash code as a Key to generate values from a Dictionary. After searching about it, I know that I also have to override Equals().
You have to override Equals(), because if two objects have the same hashcode, it doesn't mean they are to be considered equal. The hashcode simply acts as an "index" to speed up searches.
Every time you use new, an instance is created and it will not be the same instance as another instance. This is what ReferenceEquals() checks - imagine two identical bottles of soda - they're the same, but they're not the same bottle.
Equals() is meant to check whether you (the developer) want to consider two instances as equal, even though they are not the same instance.
You can implement something in this vein:
public override bool Equal(Object o) {
if (object.ReferenceEquals(o, this))
return true;
Coordinate other = o as Coordinate;
else if (null == other)
return false;
return x == other.x && y == other.y;
}
public override int GetHashCode() {
return x.GetHashCode() ^ y.GetHashCode();
}
where Equals return true if and only if instances are equal while GetHashCode() does a quick estimation (instances are not equal if they have different hash code, the reverse, however, is not true) and ensures uniform distribution of the hashes as far as it's possible (so that in Dictionary and alike structures we have roughly equally number of values per each key)
https://msdn.microsoft.com/en-us/library/336aedhh(v=vs.100).aspx
https://msdn.microsoft.com/en-us/library/system.object.gethashcode(v=vs.110).aspx
I would override the named methods like this. For the GetHashCode method I took one of several options from this question, but you can choose another if you like.
I also changed the class to immutable. You should only use immutable properties/fields to calculate the hashcode.
public class Coordinate {
public Coordinate(int p, int q) {
x = p;
y = q;
}
private readonly int x;
private readonly int y;
public int X { get { return x; } }
public int Y { get { return y; } }
public override int GetHashCode() {
unchecked // Overflow is fine, just wrap
{
int hash = (int) 2166136261;
// Suitable nullity checks etc, of course :)
hash = (hash * 16777619) ^ x.GetHashCode();
hash = (hash * 16777619) ^ y.GetHashCode();
return hash;
}
}
public override bool Equals(object obj) {
if (obj == null)
return false;
var otherCoordinate = obj as Coordinate;
if (otherCoordinate == null)
return false;
return
this.X == otherCoordinate.X &&
this.Y == otherCoordinate.Y;
}
}
Here's a simple way you to do it.
First, override the ToString() method of your class to something like this:
public override string ToString()
{
return string.Format("[{0}, {1}]", this.x, this.y);
}
Now you can easily override GetHashCode() and Equals() like this:
public override int GetHashCode()
{
return this.ToString().GetHashCode();
}
public override bool Equals(object obj)
{
return obj.ToString() == this.ToString();
}
Now if you try this:
Coordinate p1 = new Coordinate(5, 0);
Coordinate p2 = new Coordinate(5, 0);
Console.WriteLine(p1.Equals(p2));
you'll get:
True
What you try to do typically happens when you have immutable objects and such, anyway if you don't want to use a struct, you can do it like this :
public class Coord : IEquatable<Coord>
{
public Coord(int x, int y)
{
this.X = x;
this.Y = y;
}
public int X { get; }
public int Y { get; }
public override int GetHashCode()
{
object.Equals("a", "b");
// Just pick numbers that are prime between them
int hash = 17;
hash = hash * 23 + this.X.GetHashCode();
hash = hash * 23 + this.Y.GetHashCode();
return hash;
}
public override bool Equals(object obj)
{
var casted = obj as Coord;
if (object.ReferenceEquals(this, casted))
{
return true;
}
return this.Equals(casted);
}
public static bool operator !=(Coord first, Coord second)
{
return !(first == second);
}
public static bool operator ==(Coord first, Coord second)
{
if (object.ReferenceEquals(second, null))
{
if (object.ReferenceEquals(first, null))
{
return true;
}
return false;
}
return first.Equals(second);
}
public bool Equals(Coord other)
{
if (object.ReferenceEquals(other, null))
{
return false;
}
return object.ReferenceEquals(this, other) || (this.X.Equals(other.X) && this.Y.Equals(other.Y));
}
}
Note . You really should make your class immutable if you do custom equality since it could break your code if you use a hash based collection.
I think it is considered good practice to do all those overloads when you want custom equality checking like you do. Especially since when object.GetHashCode() returns the same value for two object, Dictionary and other hash based collections use the default equality operator which uses object.Equals.
Object.ReferenceEquals(Ob,Ob) determine reference equality, a.k.a if both reference point to the same allocated value, two references being equal ensure you it's the exact same object.
Object.Equals(Ob) is the virtual method in object class, by default it compares references just like Object.ReferenceEquals(Ob,Ob)
Object.Equals(Ob,Ob) calls the Ob.Equals(Ob), so yeah just a static shorthand checking for null beforehand IIRC.

Remove duplicates from a list containing a generic class

I'm trying to remove duplicates from a list containing a generic class. The generic class looks like this (stripped back example):
public class Point2D<T>
{
public T x;
public T y;
public Point2D(T x, T y)
{
this.x = x;
this.y = y;
}
}
and I've created the list like this:
List<Point2D<int>> pointList = new List<Point2D<int>>();
pointList.Add(new Point2D<int>(1,1));
pointList.Add(new Point2D<int>(1,2));
pointList.Add(new Point2D<int>(1,1));
pointList.Add(new Point2D<int>(1,3));
I tried removing the duplicates by:
pointList = pointList.Distinct().ToList();
expecting that pointList would only contain: (1,1), (1,2), (1,3) but it still contains all four points that were entered. I suspect I need my own equals or comparator method in Point2D, but I don't know if this is the case, or how they should be written (unless of course I'm just missing something simple).
To do this, you need to override Equals method:
public class Point2D<T>
{
public readonly T x;
public readonly T y;
public Point2D(T x, T y)
{
this.x = x;
this.y = y;
}
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((Point2D<T>) obj);
}
protected bool Equals(Point2D<T> other)
{
return EqualityComparer<T>.Default.Equals(x, other.x) && EqualityComparer<T>.Default.Equals(y, other.y);
}
public override int GetHashCode()
{
unchecked
{
return (EqualityComparer<T>.Default.GetHashCode(x)*397) ^ EqualityComparer<T>.Default.GetHashCode(y);
}
}
}
Also, you need to override GetHashCode. But to do it correctly, you must make your x and y readonly fields
You can use Anonymous object. How ever this will change the references. so use it only when you do not need previous references.
pointList = pointList.Select(x => new {x.x,x.y}).Distinct().Select(x => new Point2D<int>(x.x, x.y)).ToList();
You will need to implement
IEquatable<T>
interface for this custom class. Check this link for more details and sample:
https://msdn.microsoft.com/en-us/library/vstudio/bb348436(v=vs.100).aspx
I would suggest overriding == operator. This should help.

Using Generics to Multiply Integers

I have a class called GenericItem (first time using generics), suppose i wanted to multiply two items if they were of the type integer, as you can see I am trying it in the method returnCounterMultiply, but it does not allow me to multiply them although i am trying to convert them and also checking if they are of type integer.
namespace Components
{
public class GenericItem<T>
{
private T data;
private T counter;
public T Data
{
get { return data; }
set { data = value; }
}
public GenericItem(){}
public GenericItem(T _data)
{
data = _data;
}
public T returnCounterMultiply(T value)
{
int c = 0;
int d = 0;
if (counter.GetType() == typeof(int) && value.GetType() == typeof(int))
{
//cant multiply two of type T, why if i am converting to int?.
return (T)Convert.ChangeType(counter, typeof(Int32)) * (T)Convert.ChangeType(value, typeof(Int32));
}
return value;
}
}
}
I would appreciate some explanation on this as this is the first time I am working on it (this is just a sample class for understanding this GENERICS INTRO and this GENERICS CLASSES, but still having trouble understanding it.
I don't see what your trying to achieve, but if you have to do it I think you have to use an interface:
public interface IMultiplyable<T>
{
T Multiply(T x);
}
public class Int : IMultiplyable<Int>
{
private int _data { get; set; }
public Int(int data)
{
_data = data;
}
public Int Multiply(Int x)
{
return new Int(_data * x._data);
}
public override string ToString()
{
return _data.ToString();
}
}
public class GenericItem<T> where T : IMultiplyable<T>
{
private T data;
private T counter;
public T Data
{
get { return data; }
set { data = value; }
}
public GenericItem() { }
public GenericItem(T _data)
{
data = _data;
}
public T returnCounterMultiply(T value)
{
return Data.Multiply(value);
}
public override string ToString()
{
return Data.ToString();
}
}
Usage:
var a = new GenericItem<Int>(new Int(4));
MessageBox.Show(a.returnCounterMultiply(new Int(5)).ToString()); //20
In my opinion, using generics in this case is an overkill.
It would be nice that generic constraints support something like:
// T parameter is a type which overloads "+" operator...
where T : +
In your concrete case, I would argue you're going in the wrong way. Why don't you just create a class to implement such math operations where properties are typed as int?
Generics work better when T parameter (or any other parameter, of course...) can be constrained to receive types which have:
A public parameterless constructor.
Inherits or implements a class/interface
You need to constraint that T must be a class and not a struct...
When you go into a problem when using generics requires a type conversion, I believe you defeated the point of generics!
You can do something like this:
public class GenericItem<T>
{
private T data;
public T Data
{
get { return data; }
set { data = value; }
}
public GenericItem(){}
public GenericItem(T _data)
{
data = _data;
}
private Dictionary<Type, Delegate> operations =
new Dictionary<Type, Delegate>()
{
{ typeof(int), (Func<int, int, int>)((x, y) => x * y) },
{ typeof(string), (Func<string, string, string>)((x, y) => x + " " + y) },
};
public T returnCounterMultiply(T value)
{
if (operations.ContainsKey(typeof(T)))
{
var operation = (Func<T, T, T>)(operations[typeof(T)]);
return operation(data, value);
}
return value;
}
}
You just need to define, in the dictionary, one operation per valid types you're going to want to use and it just works without any converting of types (except to cast to the Func).
I had these test results:
var gii = new GenericItem<int>(42);
var xi = gii.returnCounterMultiply(2);
// xi == 84
var gis = new GenericItem<string>("Foo");
var xs = gis.returnCounterMultiply("Bar");
// xs == "Foo Bar"
Your problem has nothing to do with generics but with basic C# casting priority:
//cant multiply two of type T, why if i am converting to int?.
return
(T)Convert.ChangeType(counter, typeof(Int32))
*
(T)Convert.ChangeType(value,typeof(Int32));
You do not multiply int but T - and T being a generic type you can only use methods that are ddefined in your generics contraint, which you have none, so no multiply on it.
If you want to multiply int, then do so:
(T) (
((Int32)Convert.ChangeType(counter, typeof(Int32)))
*
((Int32)Convert.ChangeType(value,typeof(Int32)))
);
See the difference?
Basically in your code you deal with T in the multiplication, here I deal with Int32. And factually if T is a Int32 (as you tested before in the IF statement) you can just skip the convert and cast:
(T) (
((Int32)counter)
*
((Int32)value)
);
Now, generics are a bad example for maths as you can not use operations on generics - sadly. This is an abuse of the concept, but I take it was meant as a learning exercise and thus focused on that part on my answer.
I too tried this once and had to find out that there is no pretty way to do it with generics. You cannot do it as generic as in C++.
As an alternative, you may wrap your data types and use a common interface:
interface IMathOps
{
object Value { get; }
void Add(IMathOps other);
// other methods for substraction etc.
}
class IntWrapper : IMathOps
{
public int value;
public void Add(IMathOps other)
{
if(other is IntWrapper)
{
this.value += (int)other.Value;
}
}
public object Value { get { return this.value; } }
}
// class FloatWrapper : IMathOps ...
I think you should use where (generic type constraint). So it will give error at compile time if T is not int.
public T returnCounterMultiply(T value) where T : int
{
int c = 0;
int d = 0;
return c*d;
}

Non-unique elements for custom datatypes

I am trying to get list of unique elements for a custom datatype. I seriously couldn't figure out why this doesn't work. The control never reaches the Equals implementation in the below code. Could someone please help with this?
public class customobj : IEqualityComparer<customobj>
{
public string str1;
public string str2;
public customobj(string s1, string s2)
{
this.str1 = s1; this.str2 = s2;
}
public bool Equals(customobj obj1, customobj obj2)
{
if ((obj1 == null) || (obj2 == null))
{
return false;
}
return ((obj1.str1.Equals(obj2.str1)) && (obj2.str2.Equals(obj2.str2)));
}
public int GetHashCode(customobj w)
{
if (w != null)
{
return ((w.str1.GetHashCode()) ^ (w.str2.GetHashCode()));
}
return 0;
}
}
And below is the part where i am trying to retrieve distinct elements of list.
List<customobj> templist = new List<customobj> { };
templist.Add(new customobj("10", "50"));
templist.Add(new customobj("10", "50"));
List<customobj> dist = templist.Distinct().ToList();
Your class does not override base Equals() from object class, and Distinct() is using it.
Try overriding base Equals, and calling your custom Equals(Rectangle obj1, Rectangle obj2) from there.
Also, if you want to inherit from typed comparer, use IEquatable<T> , but not IEqualityComparer<Rectangle>
bool Equals(Rectangle obj1, Rectangle obj2)
is a static method of Object, so it can't be overridden.
You must override the instance Equals instead.
public override bool Equals(Object obj) {
...
}
If you want to implement IEqualityComparer in Rectangle's class you should to write something this:
List<Rectangle> dist = templist.Distinct(new Reclangle("","")).ToList();
Usually it is implements through an RectangleComparer class:
class RectangleComparer : IEqualityComparer<Rectangle>
{
public static IEqualityComparer<Rectangle> Instance { get {...} }
...
}
List<Rectangle> dist = templist.Distinct(RectangleComparer.Instance).ToList();
Or override GetHashCode and Equals =)
You are implementing the wrong interface. Your class implements IEqualityComparer<Rectangle>, not IEquatable<Rectangle>. Unless you pass in an IEqualityComparer to Distinct, it will use either IEquatable.Equals (if youe class implements it) or Object.Equals.
public class Rectangle : IEquatable<Rectangle>
{
public string width;
public string height;
public Rectangle(string s1, string s2)
{
this.width = s1; this.height = s2;
}
`IEquatable.Equals
public bool Equals(Rectangle obj2)
{
if (obj2 == null)
{
return false;
}
return ((this.width.Equals(obj2.width)) && (this.height.Equals(obj2.height)));
}
`override of object.Equals
public override bool Equals(Object(o2)
{
if(typeof(o2) == typeof(Rectangle))
return ((Rectangle)this.Equals((Rectangle)o2);
return false;
}
'override of object.GetHashCode
public override int GetHashCode()
{
return ((this.width.GetHashCode()) ^ (thisw.height.GetHashCode()));
}
}
Also, is there a particular reason why your width and height are strings and not numeric types? It seems very odd, and could lead to weird bugs such as assuming that "100" and "0100" and " 100 " are equal, when in fact they are distinct strings and will have different hash codes.

How to use the IEqualityComparer

I have some bells in my database with the same number. I want to get all of them without duplication. I created a compare class to do this work, but the execution of the function causes a big delay from the function without distinct, from 0.6 sec to 3.2 sec!
Am I doing it right or do I have to use another method?
reg.AddRange(
(from a in this.dataContext.reglements
join b in this.dataContext.Clients on a.Id_client equals b.Id
where a.date_v <= datefin && a.date_v >= datedeb
where a.Id_client == b.Id
orderby a.date_v descending
select new Class_reglement
{
nom = b.Nom,
code = b.code,
Numf = a.Numf,
})
.AsEnumerable()
.Distinct(new Compare())
.ToList());
class Compare : IEqualityComparer<Class_reglement>
{
public bool Equals(Class_reglement x, Class_reglement y)
{
if (x.Numf == y.Numf)
{
return true;
}
else { return false; }
}
public int GetHashCode(Class_reglement codeh)
{
return 0;
}
}
Your GetHashCode implementation always returns the same value. Distinct relies on a good hash function to work efficiently because it internally builds a hash table.
When implementing interfaces of classes it is important to read the documentation, to know which contract you’re supposed to implement.1
In your code, the solution is to forward GetHashCode to Class_reglement.Numf.GetHashCode and implement it appropriately there.
Apart from that, your Equals method is full of unnecessary code. It could be rewritten as follows (same semantics, ¼ of the code, more readable):
public bool Equals(Class_reglement x, Class_reglement y)
{
return x.Numf == y.Numf;
}
Lastly, the ToList call is unnecessary and time-consuming: AddRange accepts any IEnumerable so conversion to a List isn’t required. AsEnumerable is also redundant here since processing the result in AddRange will cause this anyway.
1 Writing code without knowing what it actually does is called cargo cult programming. It’s a surprisingly widespread practice. It fundamentally doesn’t work.
Try This code:
public class GenericCompare<T> : IEqualityComparer<T> where T : class
{
private Func<T, object> _expr { get; set; }
public GenericCompare(Func<T, object> expr)
{
this._expr = expr;
}
public bool Equals(T x, T y)
{
var first = _expr.Invoke(x);
var sec = _expr.Invoke(y);
if (first != null && first.Equals(sec))
return true;
else
return false;
}
public int GetHashCode(T obj)
{
return obj.GetHashCode();
}
}
Example of its use would be
collection = collection
.Except(ExistedDataEles, new GenericCompare<DataEle>(x=>x.Id))
.ToList();
If you want a generic solution that creates an IEqualityComparer for your class based on a property (which acts as a key) of that class have a look at this:
public class KeyBasedEqualityComparer<T, TKey> : IEqualityComparer<T>
{
private readonly Func<T, TKey> _keyGetter;
public KeyBasedEqualityComparer(Func<T, TKey> keyGetter)
{
if (default(T) == null)
{
_keyGetter = (x) => x == null ? default : keyGetter(x);
}
else
{
_keyGetter = keyGetter;
}
}
public bool Equals(T x, T y)
{
return EqualityComparer<TKey>.Default.Equals(_keyGetter(x), _keyGetter(y));
}
public int GetHashCode(T obj)
{
TKey key = _keyGetter(obj);
return key == null ? 0 : key.GetHashCode();
}
}
public static class KeyBasedEqualityComparer<T>
{
public static KeyBasedEqualityComparer<T, TKey> Create<TKey>(Func<T, TKey> keyGetter)
{
return new KeyBasedEqualityComparer<T, TKey>(keyGetter);
}
}
For better performance with structs there isn't any boxing.
Usage is like this:
IEqualityComparer<Class_reglement> equalityComparer =
KeyBasedEqualityComparer<Class_reglement>.Create(x => x.Numf);
Just code, with implementation of GetHashCode and NULL validation:
public class Class_reglementComparer : IEqualityComparer<Class_reglement>
{
public bool Equals(Class_reglement x, Class_reglement y)
{
if (x is null || y is null))
return false;
return x.Numf == y.Numf;
}
public int GetHashCode(Class_reglement product)
{
//Check whether the object is null
if (product is null) return 0;
//Get hash code for the Numf field if it is not null.
int hashNumf = product.hashNumf == null ? 0 : product.hashNumf.GetHashCode();
return hashNumf;
}
}
Example:
list of Class_reglement distinct by Numf
List<Class_reglement> items = items.Distinct(new Class_reglementComparer());
The purpose of this answer is to improve on previous answers by:
making the lambda expression optional in the constructor so that full object equality can be checked by default, not just on one of the properties.
operating on different types of classes, even complex types including sub-objects or nested lists. And not only on simple classes comprising only primitive type properties.
Not taking into account possible list container differences.
Here, you'll find a first simple code sample that works only on simple types (the ones composed only by primitif properties), and a second one that is complete (for a wider range of classes and complex types).
Here is my 2 pennies try:
public class GenericEqualityComparer<T> : IEqualityComparer<T> where T : class
{
private Func<T, object> _expr { get; set; }
public GenericEqualityComparer() => _expr = null;
public GenericEqualityComparer(Func<T, object> expr) => _expr = expr;
public bool Equals(T x, T y)
{
var first = _expr?.Invoke(x) ?? x;
var sec = _expr?.Invoke(y) ?? y;
if (first == null && sec == null)
return true;
if (first != null && first.Equals(sec))
return true;
var typeProperties = typeof(T).GetProperties();
foreach (var prop in typeProperties)
{
var firstPropVal = prop.GetValue(first, null);
var secPropVal = prop.GetValue(sec, null);
if (firstPropVal != null && !firstPropVal.Equals(secPropVal))
return false;
}
return true;
}
public int GetHashCode(T obj) =>
_expr?.Invoke(obj).GetHashCode() ?? obj.GetHashCode();
}
I know we can still optimize it (and maybe use a recursive?)..
But that is working like a charm without this much complexity and on a wide range of classes. ;)
Edit: After a day, here is my $10 attempt:
First, in a separate static extension class, you'll need:
public static class CollectionExtensions
{
public static bool HasSameLengthThan<T>(this IEnumerable<T> list, IEnumerable<T> expected)
{
if (list.IsNullOrEmptyCollection() && expected.IsNullOrEmptyCollection())
return true;
if ((list.IsNullOrEmptyCollection() && !expected.IsNullOrEmptyCollection()) || (!list.IsNullOrEmptyCollection() && expected.IsNullOrEmptyCollection()))
return false;
return list.Count() == expected.Count();
}
/// <summary>
/// Used to find out if a collection is empty or if it contains no elements.
/// </summary>
/// <typeparam name="T">Type of the collection's items.</typeparam>
/// <param name="list">Collection of items to test.</param>
/// <returns><c>true</c> if the collection is <c>null</c> or empty (without items), <c>false</c> otherwise.</returns>
public static bool IsNullOrEmptyCollection<T>(this IEnumerable<T> list) => list == null || !list.Any();
}
Then, here is the updated class that works on a wider range of classes:
public class GenericComparer<T> : IEqualityComparer<T> where T : class
{
private Func<T, object> _expr { get; set; }
public GenericComparer() => _expr = null;
public GenericComparer(Func<T, object> expr) => _expr = expr;
public bool Equals(T x, T y)
{
var first = _expr?.Invoke(x) ?? x;
var sec = _expr?.Invoke(y) ?? y;
if (ObjEquals(first, sec))
return true;
var typeProperties = typeof(T).GetProperties();
foreach (var prop in typeProperties)
{
var firstPropVal = prop.GetValue(first, null);
var secPropVal = prop.GetValue(sec, null);
if (!ObjEquals(firstPropVal, secPropVal))
{
var propType = prop.PropertyType;
if (IsEnumerableType(propType) && firstPropVal is IEnumerable && !ArrayEquals(firstPropVal, secPropVal))
return false;
if (propType.IsClass)
{
if (!DeepEqualsFromObj(firstPropVal, secPropVal, propType))
return false;
if (!DeepObjEquals(firstPropVal, secPropVal))
return false;
}
}
}
return true;
}
public int GetHashCode(T obj) =>
_expr?.Invoke(obj).GetHashCode() ?? obj.GetHashCode();
#region Private Helpers
private bool DeepObjEquals(object x, object y) =>
new GenericComparer<object>().Equals(x, y);
private bool DeepEquals<U>(U x, U y) where U : class =>
new GenericComparer<U>().Equals(x, y);
private bool DeepEqualsFromObj(object x, object y, Type type)
{
dynamic a = Convert.ChangeType(x, type);
dynamic b = Convert.ChangeType(y, type);
return DeepEquals(a, b);
}
private bool IsEnumerableType(Type type) =>
type.GetInterface(nameof(IEnumerable)) != null;
private bool ObjEquals(object x, object y)
{
if (x == null && y == null) return true;
return x != null && x.Equals(y);
}
private bool ArrayEquals(object x, object y)
{
var firstList = new List<object>((IEnumerable<object>)x);
var secList = new List<object>((IEnumerable<object>)y);
if (!firstList.HasSameLengthThan(secList))
return false;
var elementType = firstList?.FirstOrDefault()?.GetType();
int cpt = 0;
foreach (var e in firstList)
{
if (!DeepEqualsFromObj(e, secList[cpt++], elementType))
return false;
}
return true;
}
#endregion Private Helpers
We can still optimize it but it worth give it a try ^^.
The inclusion of your comparison class (or more specifically the AsEnumerable call you needed to use to get it to work) meant that the sorting logic went from being based on the database server to being on the database client (your application). This meant that your client now needs to retrieve and then process a larger number of records, which will always be less efficient that performing the lookup on the database where the approprate indexes can be used.
You should try to develop a where clause that satisfies your requirements instead, see Using an IEqualityComparer with a LINQ to Entities Except clause for more details.
IEquatable<T> can be a much easier way to do this with modern frameworks.
You get a nice simple bool Equals(T other) function and there's no messing around with casting or creating a separate class.
public class Person : IEquatable<Person>
{
public Person(string name, string hometown)
{
this.Name = name;
this.Hometown = hometown;
}
public string Name { get; set; }
public string Hometown { get; set; }
// can't get much simpler than this!
public bool Equals(Person other)
{
return this.Name == other.Name && this.Hometown == other.Hometown;
}
public override int GetHashCode()
{
return Name.GetHashCode(); // see other links for hashcode guidance
}
}
Note you DO have to implement GetHashCode if using this in a dictionary or with something like Distinct.
PS. I don't think any custom Equals methods work with entity framework directly on the database side (I think you know this because you do AsEnumerable) but this is a much simpler method to do a simple Equals for the general case.
If things don't seem to be working (such as duplicate key errors when doing ToDictionary) put a breakpoint inside Equals to make sure it's being hit and make sure you have GetHashCode defined (with override keyword).

Categories