I have a master list;
v2-2021 – ‘cover page$’
v2-2021 – ‘i# milestones$’
v2-2021 – ‘ii# tasks$’
v2-2021 – ‘iii# spendplan$’
I have a sub list;
v2-2021 – ‘cover page$’
v2-2021 – ‘i# milestones$’
v2-2021 – ‘ii# tasks$’
I want to make sure that all elements in my sub list exists in master list.
To solve this i have created this class;
internal class ExcelVersions
{
public string VersionNumber { get; set; }
public string TableName { get; set; }
}
I have created following objects based on this class;
List<ExcelVersions> cfirstList = new List<ExcelVersions>
{
new ExcelVersions { VersionNumber = "v2-2021", TableName = "'cover page$'" },
new ExcelVersions { VersionNumber = "v2-2021", TableName = "'i# milestones$'" },
new ExcelVersions { VersionNumber = "v2-2021", TableName = "'ii# tasks$'" },
new ExcelVersions { VersionNumber = "v2-2021", TableName = "'iii# spendplan$'" }
};
List<ExcelVersions> csecondList = new List<ExcelVersions>
{
new ExcelVersions { VersionNumber = "v2-2021", TableName = "'cover page$'" },
new ExcelVersions { VersionNumber = "v2-2021", TableName = "'i# milestones$'" },
new ExcelVersions { VersionNumber = "v2-2021", TableName = "'ii# tasks$'" }
};
//var cexceptList = csecondList.Except(cfirstList, new ExcelVersionsComparer());
var cexceptList = csecondList.Except(cfirstList);
Console.WriteLine($"\ncSecondList-->cFirstList: Value in second list that are not in first List");
foreach (var val in cexceptList)
{
Console.WriteLine($"{val.TableName}");
}
IsASubset = csecondList.All(i => cfirstList.Contains(i));
Console.WriteLine($"\ncSecondList-->cFirstList: all members of subset (cSecondList) exists in list1 (cFirstList): {IsASubset}");
}
This is the result i get;
To my surprise, none of LINQ comparison method worked on custom class. What’s wrong? The answer is in the LINQ implementation. To be correctly processed by the Except method, a type must implement the IEquatable<T> interface and provide its own Equals and GetHashCode methods.
Re-writing out custom type;
internal class ExcelVersions : IEquatable<ExcelVersions>
{
public string VersionNumber { get; set; }
public string TableName { get; set; }
public bool Equals(ExcelVersions other)
{
//check whether the compare 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 object's properteis are equal
return VersionNumber.Equals(other.VersionNumber) && TableName.Equals(other.TableName);
}
//if Equals returns true for a pair of objects
//GetHashCode must return the same value for these objects
public override int GetHashCode()
{
//Get the hash code for the version number
int hashVersionNumber = VersionNumber == null ? 0 : VersionNumber.GetHashCode();
//get the hash code for the table name
int hashTableName = TableName.GetHashCode();
//calculate the hash code for the object
return hashVersionNumber ^ hashTableName;
}
}
This time the results are;
OK. Custom class is working but what if we cannot modify the type? What if it was provided by a library and we have no way of implementing the IEquiatable<T> interface. The answer is to create our own equality comparer and pass it as a parameter to the Except method.
The equality comparer must implement the IEqualityComparer<T> interface and provide GetHashCode and Equals method like this;
internal class ExcelVersionsComparer : IEqualityComparer<ExcelVersions>
{
public bool Equals(ExcelVersions x, ExcelVersions y)
{
if (Object.ReferenceEquals(x, y))
return true;
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
return x.Equals(y);
}
public int GetHashCode(ExcelVersions excelVersion)
{
if (Object.ReferenceEquals(excelVersion, null)) return 0;
int hashVersion = excelVersion.VersionNumber == null ? 0 : excelVersion.GetHashCode();
int hashTable = excelVersion.TableName.GetHashCode();
return hashVersion ^ hashTable;
}
}
This is how we are going to pass the comparer to the Except method;
var cexceptList = csecondList.Except(cfirstList, new ExcelVersionsComparer());
These rules don’t just apply to Except method. For example, the same is true for the Distinct, Contains, Interset and Union methods. Generally, if you see that a LINQ method has an overload that accepts the IEqualityComparer<T> parameter, means that to use it with your own data type, you need to either implement IEquatable<T> in your class or create your own equality comparer.
If you want to use built-in class instead of creating custom class, consider this class;
Reference
https://stackoverflow.com/questions/16824749/using-linq-except-not-working-as-i-thought
https://grantwinney.com/how-to-compare-two-objects-testing-for-equality-in-c/
https://www.tutorialspoint.com/how-to-find-items-in-one-list-that-are-not-in-another-list-in-chash