I have a class Bar that looks like this:
public class Bar : IEquatable<Bar>
{
public string Stringbar1{ get; set; }
public string Stringbar2{ get; set; }
public string Stringbar3{ get; set; }
public string Stringbar4{ get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public EnumFoo1 Enumfoo1{ get; set; }
public bool IsBar{ get; set; }
public List<string> StringBars{ get; set; }
[BsonSerializer(SerializerType = typeof(NullableDateTimeOffsetToUtcSerializer))]
public DateTimeOffset? FooDate{ get; set; }
public string Stringbar5{ get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public EnumFoo2 EnumFoo2 { get; set; }
public string StringBar6{ get; set; }
public int Foo{ get; set; }
public Bar()
{
EnumFoo1= EnumFoo1.Unknown;
EnumFoo2= EnumFoo2.Other;
StringBars= new List<string>();
}
public bool Equals(Bar other)
{
if (other == null)
{
return false;
}
return Stringbar1 == other.Stringbar1&& Stringbar2== other.Stringbar2 && Stringbar3== other.Stringbar3 && Stringbar4== other.Stringbar4 && EnumFoo1== other.EnumFoo1 && IsBar== other.IsBar&& BothNullOrEquals(StringBars,other.StringBars) && StringBar5==other.StringBar5&& FooDate== other.FooDate && ContractType == other.ContractType && LotNumber == other.LotNumber && Rank == other.Rank;
}
public override int GetHashCode()
{
var stringbar1Hashcode = Stringbar1== null ? 0 : Stringbar1.GetHashCode();
var stringbar2HashCode = Stringbar2== null ? 0 : Stringbar2.GetHashCode();
var stringbar3CodeHashCode = Stringbar3== null ? 0 : Stringbar3.GetHashCode();
var EnumFoo1HashCode = EnumFoo1.GetHashCode();
var Stringbar4HashCode = Stringbar4== null ? 0 : Stringbar4.GetHashCode();
var isBarHashCode = IsBar.GetHashCode();
var strtingBarsHashCode = StringBars== null ? 0 : StringBars.GetHashCode();
var stringbar5HashCode = Stringbar5== null ? 0 : Stringbar5.GetHashCode();
var fooDateHashCode = FooDate== null ? 0 : FooDate.GetHashCode();
var enumFoo2HashCode= EnumFoo2.GetHashCode();
var stringBar6HasCode = StringBar6== null ? 0 : StringBar6.GetHashCode();
var fooHashCode= Foo.GetHashCode();
return stringbar1Hashcode ^ stringbar2HashCode ^ stringbar3CodeHashCode ^ EnumFoo1HashCode ^ Stringbar4HashCode ^ isBarHashCode ^ strtingBarsHashCode ^ stringbar5HashCode ^ fooDateHashCode ^ enumFoo2HashCode ^ stringBar6HasCode ^ fooHashCode ;
}
public static bool BothNullOrEquals<T>(IEnumerable<T> left, IEnumerable<T> right)
{
if (left == null && right == null)
{
return true;
}
if (left != null && right != null)
{
return left.SequenceEqual(right);
}
return false;
}
}
Equals is working as expected but it seems that I am missing something when it comes to GetHashCode cause extension methods like LINQ Distinct are not working as expected. and I know that Distinct uses the GetHashCode method to compare references so any idea what am I doing wrong ?
The problem I can see is in the
var strtingBarsHashCode = StringBars== null ? 0 : StringBars.GetHashCode();
Please, note that for List<string> StringBars HashCode is reference based:
hash codes are guaranteed to be equal if StringBars share the same reference only. However, you compare these collections by more relaxing scheme:
BothNullOrEquals(StringBars, other.StringBars)
So, StringBars doesn't have to share the same reference to be equal, they
should have equal items only. Note that GetHashCode must not return different codes for equal instances.
Don't make GetHashCode that elaborated; please, note, that GetHashCode should be a fast estimation of Equals; to be so 2 .. 3 selective properties are enough:
public override int GetHashCode() {
// Let .Net check for null and combine hash codes for you
return HashCode.Combine(Stringbar1, Stringbar2, Stringbar3);
}
Edit: If you can't use HashCode.Combine, well let it be ^, but, please, don't put many fields and properties into GetHashCode just a few the most selective ones:
public override int GetHashCode() {
// Let .Net check for null and combine hash codes for you
return (Stringbar1?.GetHashCode() ?? 0) ^
(Stringbar2?.GetHashCode() ?? 0) ^
(Stringbar3?.GetHashCode() ?? 0);
}
Related
Lets say I have two lists of the same class.
public class emailfilter
{
public string from {get;set;}
public string to {get;set;}
public string cc {get;set;}
public string subj {get;set;}
public string body {get;set;}
public string emailid {get;set;}
}
//there are two lists of type emailfilter. 1 list is formed dynamically from the config file
List<emailfilter> configfilterlist = //mock sudo code//
{
efilter tempobj = new efilter();
tempobj.from = config.from or "" if no value
tempobj.to = config.to or "" if no value
tempobj.cc = config.cc or "" if no value
tempobj.subj = config.subj or "" if no value
tempobj.body = config.body or "" if no value
configfilterlist.add(tempobj);
}
//List1 will never have an emailID
//List2 is formed from email items pulled from exchange and made into efilter objects and those do have an emailid.
//List2 will typically have all object fields populated. List1, the object fields are optional
So I want to compare/intersect list1 of filter items against list2 of email items to a combined list without duplicates that includes only the items that have all filter criteria of list1 and includes the mailid of list2.
If there's no value for a value on List1, I want to ignore that and just match on the config values provided skipping over any "" blank strings. I'm hoping there's a way to do this with lambda and linq, but I haven't seen any examples with comparison on multiple values and ignoring others like in this case emailID.
UPDATE: Thank you #wertzui for providing the answer I needed to solve this. The final solution was just slightly different so updating the post/question with essentially the final solution in case it helps another lost soul.
public class emailfilter: IEquatable<emailfilter>
{
public string from { get; set; }
public string to { get; set; }
public string cc { get; set; }
public string subj { get; set; }
public string body { get; set; }
public string emailid { get; set; }
public override int GetHashCode()
{
return System.HashCode.Combine(from, to, cc, subj, body);
}
public override bool Equals(object? obj) => Equals(obj as emailfilter);
public bool Equals(emailfilter? other)
{
return
other != null &&
(from.Contains(other.from) || other.from == "") &&
(to.Contains(other.sentto) || other.to == "") &&
(cc.Contains(other.cc) || other.cc == "") &&
(subj.Contains(other.subj) || other.subj == "") &&
(body.Contains(other.body) || other.body == "");
}
}
//emailsasfilters is List2 = all exchange emails as filter objects
var combinedSet = new HashSet<emailfilter>();
foreach (var filter in configfilterlist) //configfilterlist is List1 = filters from Config
{
if (emailsasfilters.Contains(filter))
combinedSet.Add(emailsasfilters.ElementAt(emailsasfilters.IndexOf(filter)));
}
combinedSet.Dump();
Since .Net 6, there is a new UnionBy method which you can use.
var combined = configfilterlist
.UnionBy(
exchangefilterlist,
e => new { e.from, e.to, e.cc, e.subj, e.body })
.ToList();
Another method which works with older Framework versions is, to use a HashSet<T> and implement GetHashCode and Equals
public class emailfilter: IEquatable<emailfilter>
{
public string from { get; set; }
public string to { get; set; }
public string cc { get; set; }
public string subj { get; set; }
public string body { get; set; }
public string emailid { get; set; }
public override int GetHashCode()
{
return System.HashCode.Combine(from, to, cc, subj, body);
}
public override bool Equals(object? obj) => Equals(obj as emailfilter);
public bool Equals(emailfilter? other)
{
return
other != null &&
from == other.from &&
to == other.to &&
cc == other.cc &&
subj == other.subj &&
body == other.body;
}
}
var combinedSet = new HashSet<emailfilter>(configfilterlist);
foreach (var email in exchangefilterlist)
{
combinedSet.Add(email);
}
combinedSet.Dump();
This question already has answers here:
Using Linq Except not Working as I Thought
(5 answers)
Closed 4 years ago.
I have two Generic List object, I want to get records which are not matching in second Generic list object. Below is my code. But it returning all records.
I want to ignore matching record in first list.
public class CuratedIncludeUid
{
public string type { get; set; }
public string uid { get; set; }
}
List<CuratedIncludeUid> newUids = new List<CuratedIncludeUid>();
newUids.Add(new CuratedIncludeUid { type = "series", uid = "600" });
List<CuratedIncludeUid> liExistingUids = new List<CuratedIncludeUid>();
liExistingUids.Add(new CuratedIncludeUid { type = "series", uid = "600" });
liExistingUids.Add(new CuratedIncludeUid { type = "series", uid = "200" });
var ied = liExistingUids.Except(newUids).ToList(); ;
foreach (var row in ied)
{
Console.WriteLine("Uid:" + row.uid + "type:" + row.type);
}
Console.Read();
I am getting Output as below
Uid:600type:series
Uid:200type:series
**My expected output as below
Uid:200type:series**
Either override Equals and GetHashCode
public class CuratedIncludeUid
{
public string type { get; set; }
public string uid { get; set; }
protected bool Equals(CuratedIncludeUid other)
{
return string.Equals(type, other.type) && string.Equals(uid, other.uid);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return Equals((CuratedIncludeUid) obj);
}
public override int GetHashCode()
{
unchecked
{
return ((type != null ? type.GetHashCode() : 0) * 397) ^ (uid != null ? uid.GetHashCode() : 0);
}
}
}
Or passing an IEqualityComparer to Except
public class CuratedIncludeUidEqualityComparer : IEqualityComparer<CuratedIncludeUid>
{
public bool Equals(CuratedIncludeUid x, CuratedIncludeUid y)
{
if (ReferenceEquals(x, y)) return true;
if (ReferenceEquals(x, null) || ReferenceEquals(y, null)) return false;
return string.Equals(x.type, y.type) && string.Equals(x.uid, y.uid);
}
public int GetHashCode(CuratedIncludeUid obj)
{
unchecked
{
return ((obj.type != null ? obj.type.GetHashCode() : 0) * 397) ^ (obj.uid != null ? obj.uid.GetHashCode() : 0);
}
}
}
var ied = liExistingUids.Except(newUids, new CuratedIncludeUidEqualityComparer()).ToList();
Either, you can implement Equals and GetHashCode or IEqualityComparer, or you can also do the following:
With All:
var ied = liExistingUids.Except(newUids).ToList();
liExistingUids
.Where(x => newUids.All(y => y.type != x.type && y.series != x.series))
.ToList();
With Any:
liExistingUids
.Where(x => !newUids.Any(y => y.type == x.type && y.series == x.series))
.ToList();
I'm trying to set up a way to compare some nested lists with objects that I'm importing from MongoDB. I have already set up the lists object:
public class SecurityGroup
{
public ObjectId Id { get; set; }
public string GroupID { get; set; }
public string GroupName{ get; set; }
public List<IpPermission> IpPermissions { get; set; }
public override string ToString()
{
return string.Format("groupid : {0}, groupname : {1} ", GroupID, GroupName );
}
With in that class I also have an overridden Equals method in place.
public override bool Equals(object obj)
{
SecurityGroup secGroup = obj as SecurityGroup;
if (secGroup == null)
{
return false;
}
if (!string.Equals(GroupID, secGroup.GroupID, StringComparison.OrdinalIgnoreCase))
{
return false;
}
if (!string.Equals(GroupName,secGroup.GroupName, StringComparison.OrdinalIgnoreCase))
{
return false;
}
I'm not sure if I need to post the class used for the nested loop but there's an IEquatable interface and the entireity of this class is EXACTLY like the SecurityGroup class I just posted.
//Compare IpPermissions
var diff1 = IpPermissions.Except(secGroup.IpPermissions);
var diff2 = secGroup.IpPermissions.Except(IpPermissions);
if (diff1.Any() || diff2.Any())
{
return false;
}
return true;
}
Now here's the Hashcode method that I set up:
public override int GetHashCode()
{
unchecked
{
const int HashingBase = (int)2166136261;
const int HashingMultiplier = 16777619;
int hash = HashingBase;
hash = (hash * HashingMultiplier) ^ (!Object.ReferenceEquals(null, IpPort) ? IpPort.GetHashCode() : 0);
hash = (hash * HashingMultiplier) ^ (!Object.ReferenceEquals(null, IpProtocol) ? IpProtocol.GetHashCode() : 0);
hash = (hash * HashingMultiplier) ^ (!Object.ReferenceEquals(null, IpRanges) ? IpRanges.GetHashCode() : 0);
return hash;
}
}
}
}
That essentially conludes the code I have in place to set up the framework for my comparesList method. Now here's where I'm having issues. I'm trying to set up the compare lists method and it's just giving me underscored red lines on the 'public static' part. The thing I can't figure out is the error it's giving me for the 'foreach' statements. It's saying, "Cannot convert element type 'AwsInstanceProfile1.Entity.SecurityGroup' to iterator type 'Amazon.EC2.Model.SecurityGroup'" Which is really weird because I should in theory have the framework set up to allow the lists into these objects. Here's the rest of the method:
public static bool CompareLists(List<Entity.SecurityGroup> list1, List<Entity.SecurityGroup> list2) =>
{
if (list1 == null || list2 == null)
return list1 == list2;
Dictionary<SecurityGroup, int> hash = new Dictionary<SecurityGroup, int>();
foreach (SecurityGroup secGroup in list1)
{
if (hash.ContainsKey(secGroup))
{
hash[secGroup]++;
}
else
{
hash.Add(secGroup, 1);
}
}
foreach (SecurityGroup secGroup in list2)
{
if (!hash.ContainsKey(secGroup) || hash[secGroup] == 0)
{
return false;
}
hash[secGroup]--;
}
return true;
}
I have scenario to check
1) if the any prop (EmployeeObject), from empDb appear in empXml , return true. Else return false
public class EmployeeObject
{
public Int32 Id { get; set; }
public string Title { get; set; }
public string Desc { get; set; }
.....
}
IList<EmployeeObject> empDb = PopulateFromDb(); //calling ado.net
IList<EmployeeObject> empXml = PopulateFromXml(); //deserializing xml
So let me get this straight, I'm trying to determine if list empXml is a subset of list empDb ?
Tried so far; but it returns false even thought i have check the data in both list and it should have return true unless i am doing something wrong in my expression.
//at least one MATCH
empDb.Any(a => empXml.Contains(a));
or
//at least one EXACT match
empDb.Any(x => empXml.Contains(y => x.Equals(y)));
If you don't have Equals and GetHashCode implemented in EmployeeObject class then employees will be compared by reference. And you will definitely have different instances here, because first list is created when you read data from database, and second list is created when you are deserializing xml. So, even employees with same values of all fields will be considered different.
If you want to check matches only by employee Id, then you can project sequences to ids and then use Intersect to check if match exist
// at least one employee with equal Id
empDb.Select(e => e.Id).Intersect(empXml.Select(e => e.Id)).Any()
If you want to compare employees by value of their fields instead of their references, you have several options. If you can't or don't want to change implementation of EmployeeObject class and override its Equals and GetHashCode methods, then you can create custom comparer for employees:
public class EmployeeComparer : IEqualityComparer<EmployeeObject>
{
public bool Equals(EmployeeObject x, EmployeeObject y)
{
return x.Id == y.Id
&& x.Title == y.Title
&& x.Desc == y.Desc;
}
public int GetHashCode(EmployeeObject obj)
{
int code = 19;
code = code * 23 + obj.Id.GetHashCode();
code = code * 23 + obj.Title.GetHashCode();
code = code * 23 + obj.Desc.GetHashCode();
return code;
}
}
Then you can use this comparer:
empDb.Intersect(empXml, new EmployeeComparer()).Any()
Or you can project your employees to anonymous objects (which have default implementation of Equals and GetHashCode):
empDb.Select(e => new { e.Id, e.Title, e.Desc })
.Intersect(empXml.Select(e => new { e.Id, e.Title, e.Desc })).Any()
Or override these methods:
public class EmployeeObject
{
public Int32 Id { get; set; }
public string Title { get; set; }
public string Desc { get; set; }
public override int GetHashCode()
{
int code = 19;
code = code * 23 + Id.GetHashCode();
code = code * 23 + Title.GetHashCode();
code = code * 23 + Desc.GetHashCode();
return code;
}
public override bool Equals(object obj)
{
EmployeeObject other = obj as EmployeeObject;
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
return Id == other.Id &&
Title == other.Title && Desc == other.Desc;
}
}
And your code will work. Or you can use Intersect:
empDb.Intersect(empXml).Any()
If Id is the primary key of the entity you might want to write:
var set = new HashSet<int>(empXml.Select(x => x.Id)); //For faster lookup
empDb.Any(a => set.Contains(a.Id));
But if you need to match on all properties you need to override Equals and GetHashCode. (this implementation also match on null values for the properties)
public class EmployeeObject : IEquatable<EmployeeObject>
{
public bool Equals(EmployeeObject other)
{
return Id == other.Id &&
string.Equals(Title, other.Title) &&
string.Equals(Desc, other.Desc);
}
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((EmployeeObject) obj);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = Id;
hashCode = (hashCode*397) ^ (Title != null ? Title.GetHashCode() : 0);
hashCode = (hashCode*397) ^ (Desc != null ? Desc.GetHashCode() : 0);
return hashCode;
}
}
public Int32 Id { get; set; }
public string Title { get; set; }
public string Desc { get; set; }
}
And the write:
var set = new HashSet<EmployeeObject>(empXml); //For faster lookup
empDb.Any(a => set.Contains(a));
I have to test the equality of trees. In other other words objects which contains List<T> with childs and the childs also contains List<T> with childs and so on.
I've found that you can test List with CollectionAssert, however it does not work that well with composites.
Any suggestions? MSUnit is my test library.
Example
IReagentComposed bronzeBarParsed = (from n in composedCrafts where n.ItemId == 2841 select n).Single();
IReagentComposed bronzeBar = new Craft()
{
ItemId = 2841,
Profession = Profession.Mining,
Quantity = 0,
QuantityCrafted = 0,
Skill = 50,
Reagents = new List()
{
new Craft()
{
ItemId = 2840,
Quantity = 0,
Skill = 1,
Profession = Profession.Mining,
Reagents = new List()
{
new Reagent()
{
ItemId = 2770,
Quantity = 1
}
}
},
new Craft()
{
ItemId = 3576,
Quantity = 0,
Skill = 50,
Profession = Profession.Mining,
Reagents = new List()
{
new Reagent()
{
ItemId = 2771,
Quantity = 1
}
}
}
}
};
Assert.AreEqual(bronzeBar, bronzeBarParsed);
Craft and Reagent
public class Craft : IReagentComposed
{
public int QuantityCrafted { get; set; }
public int Quantity { get; set;}
public int ItemId { get; set; }
public int Skill { get; set; }
public Profession Profession { get; set; }
public IEnumerable Reagents { get; set; }
public override bool Equals(object other)
{
if (other == null || GetType() != other.GetType()) return false;
IReagentComposed o = other as IReagentComposed;
return o != null && this.Quantity == o.Quantity &&
this.ItemId == o.ItemId &&
this.Profession == o.Profession &&
this.Reagents == o.Reagents && //also tried Equals
this.Skill == o.Skill;
}
public override int GetHashCode()
{
return 0;
}
}
public class Reagent : IReagent
{
public int ItemId { get; set; }
public int Quantity { get; set; }
public override bool Equals(object other)
{
if (other == null || GetType() != other.GetType()) return false;
IReagent o = other as IReagent;
return o != null && o.ItemId == this.ItemId && o.Quantity == this.Quantity;
}
public override int GetHashCode()
{
return 0;
}
}
return o != null && this.Quantity == o.Quantity &&
this.ItemId == o.ItemId &&
this.Profession == o.Profession &&
this.Reagents == o.Reagents && //also tried Equals
this.Skill == o.Skill;
The Reagents are unlikely to match, they are distinct List objects. List<> doesn't override Equals although it isn't that clear what actual type you use. Write a little helper function that takes two lists of reagents and checks for equality. You'd typically start at comparing List<>.Count and then work down the elements one by one.
Added a extension method for IEnumberable<T>:
public static class IEnumberableExtensions
{
public static bool AreEnumerablesEqual<T>(this IEnumerable<T> x, IEnumerable<T> y)
{
if (x.Count() != y.Count()) return false;
bool equals = false;
foreach (var a in x)
{
foreach (var b in y)
{
if (a.Equals(b))
{
equals = true;
break;
}
}
if (!equals)
{
return false;
}
equals = false;
}
return true;
}
}