I would like to compare two list of nested objects. If the parent objects Id differ and/or any of the childrens Id or Baz property differs, I want to consider them changed.
I've implemented my own version of Equals and GetHashCode below, but despite using my own equalitycomparer, Except() still yields a result, while I expect the objects to be equal.
var foo1 = new Foo
{
Id = 1,
Bars = new List<Bar>
{
new Bar
{
Id = 1,
Baz = 1.5
},
new Bar
{
Id = 1,
Baz = 1.5
}
}
};
var foo2 = new Foo
{
Id = 1,
Bars = new List<Bar>
{
new Bar
{
Id = 1,
Baz = 1.5
},
new Bar
{
Id = 1,
Baz = 1.5
}
}
};
var diff = new[] { foo1 }.Except(new[] { foo2 });
public class Foo
{
private sealed class IdBarsEqualityComparer : IEqualityComparer<Foo>
{
public bool Equals(Foo x, Foo y)
{
if (ReferenceEquals(x, y)) return true;
if (ReferenceEquals(x, null)) return false;
if (ReferenceEquals(y, null)) return false;
if (x.GetType() != y.GetType()) return false;
return x.Id == y.Id && Equals(x.Bars, y.Bars);
}
public int GetHashCode(Foo obj)
{
unchecked
{
return (obj.Id * 397) ^ (obj.Bars != null ? obj.Bars.GetHashCode() : 0);
}
}
}
public static IEqualityComparer<Foo> IdBarsComparer { get; } = new IdBarsEqualityComparer();
public int Id { get; set; }
public List<Bar> Bars { get; set; }
}
public class Bar
{
protected bool Equals(Bar other)
{
return Id == other.Id && Baz.Equals(other.Baz);
}
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((Bar) obj);
}
public override int GetHashCode()
{
unchecked
{
return (Id * 397) ^ Baz.GetHashCode();
}
}
public int Id { get; set; }
public double Baz { get; set; }
}
There are three things wrong in your code:
You are not passing the equality comparer to Except method, so it's not being used.
Your GetHashCode implementation in Foo is wrong, it returns different results for same objects, so the Equals method is never called.
You are calling equals on two lists: Equals(x.Bars, y.Bars), this checks for reference equality. You can use SequenceEqual instead to compare elements one by one: x.Bars.SequenceEqual(y.Bars)
Related
So I have two Lists.
List<Farmer> CSVFarmer;
List<Farmer> Farmers;
CSVFarmer List gets its items from a method that will read a csv file.
Farmers List gets its items from a table in a sql database;
Now what I want to do is compare the two lists and return a list of non matching items;
For example if List CSVFarmer has:
FarmerName ContractNumber ContactNumber
John 2468 12345
Mike 13579 15790
And List Farmers has:
FarmerName ContractNumber ContactNumber
Mike 13579 15790
The list being returned should only have one item in it : Farmer John.
Farmer Class:
public class Farmer:INotifyPropertyChanged
{
int _id;
string _firstName;
string _farmerNo;
string _contactNumber;
public Farmer()
{
_firstName = string.Empty;
_farmerNo = string.Empty;
_contactNumber = string.Empty;
}
public int Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("Id");
}
}
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
OnPropertyChanged("FirstName");
}
}
public string FarmerNo
{
get { return _farmerNo; }
set
{
_farmerNo = value;
OnPropertyChanged("FarmerNo");
}
}
public string ContactNumber
{
get { return _contactNumber; }
set
{
_contactNumber = value;
OnPropertyChanged("ContactNumber");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged (string property)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
Ive tried this:
public class vmUserManagement
{
public List<Farmer> Farmer { get; set; }
public List<Farmer> CSVFarmers { get; set; }
public List<Farmer> Farmers { get; set; }
public vmUserManagement()
{
CSVFarmers = new List<Farmer>();
Farmers = new List<Farmer>();
Farmer = new List<Farmer>();
}
public List<Farmer> getAllFarmers()
{
Farmers = RepoDapper.getAllFarmers();
return Farmers;
}
public List<Farmer> CSVImportFarmer()
{
OpenFileDialog openFile = new OpenFileDialog();
openFile.DefaultExt = ".csv";
openFile.Filter = "(.csv) | *.csv";
var browseFile = openFile.ShowDialog();
if (browseFile == true)
{
string FilePath = openFile.FileName;
List<Farmer> values = File.ReadAllLines(FilePath).Select(v => FromFarmerCsv(v)).ToList();
CSVFarmers = values;
}
return CSVFarmers;
}
public static Farmer FromFarmerCsv(string csvLine)
{
string[] values = csvLine.Split(',');
Farmer farmer = new Farmer();
farmer.FirstName = values[0];
farmer.FarmerNo = values[1];
farmer.ContactNumber = values[2];
return farmer;
}
public List<Farmer> validateFarmerList()
{
foreach (var a in CSVFarmers)
{
foreach (var b in Farmers)
{
if (a != b)
{
Farmer.Add(a);
}
}
}
return Farmer;
}
}
The problem I'm having is that I will end up with two entries in List Farmer. Both for Farmer John and Farmer Mike. When I should only be getting a List containing Farmer John. Why is that?
I've also tried using Except:
public List<Farmer> validateFarmerList()
{
Farmer = CSVFarmers.Except(Farmers).ToList();
return Farmer;
}
But I still get two items in my Farmer List (Farmer John and Mike) instead of one.
Am I missing something? Any help will be much appreciated.
You have't overridden Equals and GethashCode in your Farmer class. That's why (a != b) doesn't work and also Enumerable.Except fails for the same reason: only references are compared and both are different instances.
How should .NET know that the ContractNumber of the farmer is relevant to identify him? One wayy is to tell it by overriding Equals and GetHashCode:
public class Farmer : IEquatable<Farmer>
{
public string FarmerName { get; set; }
public string ContractNumber { get; set; }
// .... other properties etc
public bool Equals(Farmer other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(ContractNumber, other.ContractNumber);
}
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((Farmer) obj);
}
public override int GetHashCode()
{
return (ContractNumber != null ? ContractNumber.GetHashCode() : 0);
}
}
Now you can use Except:
Farmer = CSVFarmers.Except(Farmers).ToList();
2nd way is to implement a custom IEqualityComparer<Farmer>, f.e if you can't change the Farmer class itself or you don't want to change it's behaviour and just want a custom comparer:
public class FarmerContractComparer : IEqualityComparer<Farmer>
{
public bool Equals(Farmer x, Farmer y)
{
if (ReferenceEquals(x, y)) return true;
if (ReferenceEquals(null, x) || ReferenceEquals(null, y)) return false;
return x.ContractNumber == y.ContractNumber;
}
public int GetHashCode(Farmer obj)
{
return (obj.ContractNumber != null ? obj.ContractNumber.GetHashCode() : 0);
}
}public class FarmerContractComparer : IEqualityComparer<Farmer>
{
public bool Equals(Farmer x, Farmer y)
{
if (ReferenceEquals(x, y)) return true;
if (ReferenceEquals(null, x) || ReferenceEquals(null, y)) return false;
return x.ContractNumber == y.ContractNumber;
}
public int GetHashCode(Farmer obj)
{
return (obj.ContractNumber != null ? obj.ContractNumber.GetHashCode() : 0);
}
}
You can use this comparer in many LINQ methods, for example also in Enumerable.Except:
Farmer = CSVFarmers.Except(Farmers, new FarmerContractComparer()).ToList();
This approach has the advantage that you could provide different comparers for different tasks.
3rd approach: use LINQ and don't create a new class(less reusable and efficient but less work):
Farmer = CSVFarmers.Where(f => !Farmers.Any(f2 => f.ContractNumber == f2.ContractNumber)).ToList();
You will need to either create a class implementing IEqualityComparer<Farmer> and pass an instance of that as a second parameter to .Except() or implement IEquatable<Farmer> on your Farmer class.
The other answer already has a good implementation of the latter. If you always want farmers to be equal when their contract number is equal, use that one.
So if you want your Except work on contract numbers, but in other places you want other criteria, you need the first option:
class FarmerEqualWhenContractEqualComparer : IEqualityComparer<Farmer>
{
public bool Equals(Farmer x, Farmer y)
{
//Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
//Check whether the farmers' contracts are equal.
return x.ContractNumber == y.ContractNumber;
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public int GetHashCode(Farmer farmer)
{
//Check whether the object is null
if (Object.ReferenceEquals(product, null)) return 0;
//Get hash code for the Name field if it is not null.
return farmer.ContractNumber?.GetHashCode() ?? 0;
}
}
Then you can do:
var changedOrNew = CSVFarmers.Except(Farmers, new FarmerEqualWhenContractEqualComparer()).ToList();
I have a collection of objects where I want to find distinct values based on several properties.
I could do this:
var distinct = myValues.GroupBy(p => new { A = p.P1, B = p.P2 });
But I want to encapsulate the equality sementics. Something like this:
public interface IKey<T>
{
bool KeyEquals(T other);
}
public class MyClass : IKey<MyClass>
{
public string P1 { get; set; }
public string P2 { get; set; }
public bool KeyEquals(MyClass other)
{
if(object.ReferenceEquals(this, other)
return true;
if(other == null)
return false;
return this.P1 == other.P1 && this.P2 == other.P2;
}
}
Is there an O(N) way to get distinct values using my KeyEquals function?
If you can't change MyClass, you can implement an IEqualityComparer:
class MyClassComparer : IEqualityComparer<MyClass>
{
public bool Equals(MyClass m1, MyClass m2)
{
return m1.KeyEquals(m2);
}
public int GetHashCode(MyClass m)
{
return (m.P1.GetHashCode() *23 ) + (m.P2.GetHashCode() * 17);
}
}
And pass it to GroupBy
var distinct = myValues.GroupBy(p => p, new MyClassComparer());
I'm using xUnit and it doesn't have a way to determine if 2 IEnumerable<T> are equal if T is custom type.
I've tried using LINQ SequenceEqual but again as the instances of T are different this returns false;
Here is a basic test with a non-working IEqualityComparer
[Fact]
public void FactMethodName()
{
var one = new[] { new KeywordSchedule() { Id = 1 } };
var two = new[] { new KeywordSchedule() { Id = 1 } };
Assert.Equal(one, two, new KeywordScheduleComparer());
}
public class KeywordScheduleComparer : IEqualityComparer<IEnumerable<KeywordSchedule>>
{
public bool Equals(IEnumerable<KeywordSchedule> x, IEnumerable<KeywordSchedule> y)
{
return Object.ReferenceEquals(x, y) || (x != null && y != null && x.SequenceEqual(y));
}
public int GetHashCode(IEnumerable<KeywordSchedule> obj)
{
if (obj == null)
return 0;
return unchecked(obj.Select(e => e.GetHashCode()).Aggregate(0, (a, b) => a + b)); // BAD
}
}
I'm using this in an integration test, so I insert data from a IEnumerable into a DB at the start, then call my SUT to retrieve data from DB and compare.
If you can help me get a collection comparison working I'd appreciate it!
I just verified that this works fine with xUnit.net 1.9.2:
public class MyClass
{
public int ID { get; set; }
public string Name { get; set; }
}
public class MyClassComparer : IEqualityComparer<MyClass>
{
public bool Equals(MyClass x, MyClass y)
{
return x.ID == y.ID;
}
public int GetHashCode(MyClass obj)
{
return obj.ID.GetHashCode();
}
}
public class ExampleTest
{
[Fact]
public void TestForEquality()
{
var obj1 = new MyClass { ID = 42, Name = "Brad" };
var obj2 = new MyClass { ID = 42, Name = "Joe" };
Assert.Equal(new[] { obj1 }, new[] { obj2 }, new MyClassComparer());
}
}
So I'm not 100% clear why you need the extra comparer. Just the single comparer should be sufficient.
Well, your implementation is pending. You implemented custom comparer for IEnumerable<KeywordSchedule> but forgot to implement the same for KeywordSchedule.
x.SequenceEqual Still uses Comparer<T>.Default so it goes for reference comaprison and hence result is false.
public class KScheduleComparer : IEqualityComparer<KeywordSchedule>
{
public bool Equals(KeywordSchedule x, KeywordSchedule y)
{
return x.Id == y.Id;
}
public int GetHashCode(KeywordSchedule obj)
{
return obj.GetHashCode();
}
}
Then modify your Equals method in KeywordScheduleComparer class as below
public class KeywordScheduleComparer : IEqualityComparer<IEnumerable<KeywordSchedule>>
{
public bool Equals(IEnumerable<KeywordSchedule> x, IEnumerable<KeywordSchedule> y)
{
return Object.ReferenceEquals(x, y) || (x != null && y != null && x.SequenceEqual(y, new KScheduleComparer()));
}
public int GetHashCode(IEnumerable<KeywordSchedule> obj)
{
if (obj == null)
return 0;
return unchecked(obj.Select(e => e.GetHashCode()).Aggregate(0, (a, b) => a + b)); // BAD
}
}
You could do this more elegantly using FluentAssertions library. It has plenty assertion methods for collections.
public class MyClass
{
public int ID { get; set; }
public string Name { get; set; }
protected bool Equals(MyClass other)
{
return ID == other.ID;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((MyClass) obj);
}
public override int GetHashCode()
{
unchecked
{
return (ID*397) ^ (Name != null ? Name.GetHashCode() : 0);
}
}
}
public class ExampleTest
{
[Fact]
public void TestForEquality()
{
var obj1 = new MyClass { ID = 42, Name = "Rock" };
var obj2 = new MyClass { ID = 42, Name = "Paper" };
var obj3 = new MyClass { ID = 42, Name = "Scissors" };
var obj4 = new MyClass { ID = 42, Name = "Lizard" };
var list1 = new List<MyClass> {obj1, obj2};
list1.Should().BeEquivalentTo(obj3, obj4);
}
}
my class:
public class myClass
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
public int D { get; set; }
}
and main example:
Dictionary<myClass, List<string>> dict = new Dictionary<myClass, List<string>>();
myClass first = new myClass();
first.A = 2;
first.B = 3;
myClass second = new myClass();
second.A = 2;
second.B = 3;
second.C = 5;
second.D = 6;
dict.Add(first, new List<string>());
if (dict.ContainsKey(second))
{
//
//should come here and update List<string> for first (and only in this example) key
//
}
else
{
//
//if myFirst object has difference vlues of A or B properties
//
dict.Add(second, new List<string>());
}
How to do this?
If you always want the dictionary only to compare on A and B, you have two options. Either use the constructor that implements IEqualityComparer<TKey> and put your comparison logic there, or have your class implement IEquateable<T> GetHashCode and Equals so the default comparer will give you the results you are looking for.
If you only want to compare on A and B in your one situation you will need to use the .Keys property and the Linq extension method Contains that allows you to pass in a IEqualityComparer<T>. However, when doing it this way you loose the speed benefits of using a Dictionary, so use it sparingly.
public class MyClassSpecialComparer : IEqualityComparer<myClass>
{
public bool Equals (myClass x, myClass y)
{
return x.A == y.A && x.B == y.B
}
public int GetHashCode(myClass x)
{
return x.A.GetHashCode() + x.B.GetHashCode();
}
}
//Special case for when you only want it to compare this one time
//NOTE: This will be much slower than a normal lookup.
var myClassSpecialComparer = new MyClassSpecialComparer();
Dictionary<myClass, List<string>> dict = new Dictionary<myClass, List<string>>();
//(Snip)
if (dict.Keys.Contains(second, myClassSpecialComparer ))
{
//
//should come here and update List<string> for first (and only in this example) key
//
}
//If you want it to always compare
Dictionary<myClass, List<string>> dict = new Dictionary<myClass, List<string>>(new MyClassSpecialComparer());
By default, comparison puts objects into buckets based on their hash code. A detailed comparison is then performed (by calling Equals) if two hash codes are the same. If your class neither provides GetHashCode or implements equality, the default object.GetHashCode will be used--in which case nothing specific to your class will be used for value comparison semantics. Only the same reference will be found. If you don't want this, implement GetHashCode and implement equality.
For example:
public class myClass
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
public int D { get; set; }
public bool Equals(myClass other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return other.A == A && other.B == B && other.C == C && other.D == D;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof (myClass)) return false;
return Equals((myClass) obj);
}
public override int GetHashCode()
{
unchecked
{
int result = A;
result = (result*397) ^ B;
result = (result*397) ^ C;
result = (result*397) ^ D;
return result;
}
}
}
Override in your myClass:
GetHashCode method
Equals method
To implement GetHashCode method you can just XOR GetHashCodes from your integer properties.
Optionally override ToString method and implement IEquatable interface
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
namespace UnitTest.Model
{
[TestFixture]
public class SampleEquatableObjectTest
{
[Test]
public void TwoIdenticalUsersComparedEqualTrue()
{
var user1 = new SampleObject { Id = 1, Name = "Test User" };
var user2 = new SampleObject { Id = 1, Name = "Test User" };
Assert.IsTrue(user1.Equals(user2));
}
[Test]
public void TwoDifferentUsersComparedEqualFalse()
{
var user1 = new SampleObject { Id = 1, Name = "Test User 1" };
var user2 = new SampleObject { Id = 2, Name = "Test User 2" };
Assert.IsFalse(user1.Equals(user2));
}
[Test]
public void CollectionOfUsersReturnsDistinctList()
{
var userList = new List<SampleObject>
{
new SampleObject {Id = 1, Name = "Test User"},
new SampleObject {Id = 1, Name = "Test User 1"},
new SampleObject {Id = 2, Name = "Test User 2"}
};
Assert.AreEqual(userList.Count, 3);
var result = userList.Distinct();
Assert.AreEqual(result.Count(), 2);
var multipleTest = (from r in result group r by new { r.Id } into multGroup where multGroup.Count() > 1 select multGroup.Key).Any();
Assert.IsFalse(multipleTest);
}
public class SampleObject : IEquatable<SampleObject>
{
public int Id { get; set; }
public string Name { get; set; }
public bool Equals(SampleObject other)
{
if (ReferenceEquals(this, other))
return true;
if (ReferenceEquals(other, null) || ReferenceEquals(this, null))
return false;
return Id.Equals(other.Id);
}
}
}
}
The distinct method in this test case does not return a distinct list. The assert for count will fail. I looked at other similar questions and Microsoft examples but they look exactly like the code I have in the test. Any input?
You also need to override the GetHashCode() and Equals methods from the Object class. For more information see this corresponding FXCOP violation.
Then your tests will work like expected.
public class SampleObject : IEquatable<SampleObject>
{
public int Id { get; set; }
public string Name { get; set; }
public bool Equals(SampleObject other)
{
if (ReferenceEquals(this, other))
return true;
if (ReferenceEquals(other, null) || ReferenceEquals(this, null))
return false;
return Id.Equals(other.Id);
}
public override int GetHashCode()
{
return Id;
}
public override bool Equals(object obj)
{
return Equals(obj as SampleObject);
}
}
You're implementing an interface without defining both members to the specification of the interface. Your code needs to look like this:
public bool Equals(SampleObject x, SampleObject y)
{
if (ReferenceEquals(x, y))
return true;
if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
return false;
return x.Id.Equals(y.Id);
}
public int GetHashCode(SampleObject obj)
{
public int GetHashCode(SampleObject obj)
{
if (Object.ReferenceEquals(obj, null)) return 0;
int hashId = obj.Id == null ? 0 : obj.Id.GetHashCode();
int hashName = obj.Name == null ? 0 : obj.Name.GetHashCode();
return hashId ^ hashName; // or what ever you want you hash to be, hashID would work just as well.
}
}