SequenceEqual is true but HashSet.SetEquals is false - c#

I've been experimenting with implementing Set equality (ie List comparisons where the order is irrelevant) and after reading SO questions like this and this, wrote the following simple extension:
public static bool SetEqual<T>(this IEnumerable<T> enumerable, IEnumerable<T> other)
{
if (enumerable == null && other == null)
return true;
if (enumerable == null || other == null)
return false;
var setA = new HashSet<T>(enumerable);
return setA.SetEquals(other);
}
However, I came across a simple data structure for which this approach does not work, while Enumerable.SequenceEqual does.
public class Test : IEquatable<Test>
{
public Guid Id { get; set; }
public List<Test> RelatedTest { get; set; }
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof(Test)) return false;
return Equals((Test)obj);
}
public bool Equals(Test other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return other.Id.Equals(Id) &&
RelatedTest.SetEqual(other.RelatedTest);
}
}
Given this object, this test succeeds:
[Test]
public void SequenceEqualTest()
{
var test1 = new List<Test> {new Test()};
var test2 = new List<Test> {new Test() };
Assert.That(test1.SequenceEqual(test2), Is.True);
}
But this test fails:
[Test]
public void SetEqualTest()
{
var test1 = new List<Test> {new Test()};
var test2 = new List<Test> {new Test()};
Assert.That(test1.SetEqual(test2), Is.True);
}
Does anyone have an explanation?

Yes, you didn't override GetHashCode in your Test class, so the HashSet is unable to effectively group items into buckets according to possible equality. See this question for more details: Why is it important to override GetHashCode when Equals method is overridden?

Related

How should I implement equality checking for classes that have objects of other classes as member?

I have a Student class that has an object of class Name.
At this point, the equality check of the Student class returns false, and I don't know why.
public class Student : IEquatable<Student>
{
public Name Name { get; }
public Student(Name name) => Name = name;
public bool Equals(Student other)
{
if (ReferenceEquals(this, other))
return true;
if (ReferenceEquals(other, null))
return false;
return Name == (other.Name);
}
}
public class Name : IEquatable<Name>
{
public string First { get; }
public Name(string first) => First = first;
public bool Equals(Name other)
{
if (ReferenceEquals(this, other))
return true;
if (ReferenceEquals(other, null))
return false;
return First == other.First;
}
}
var s1 = new Student(new Name("A"));
var s2 = new Student(new Name("A"));
Console.WriteLine(s1.Equals(s2).ToString());
Of course, doing the equality check this way will return true.
var s1 = new Student(new Name("A"));
var s2 = s1;
Console.WriteLine(s1.Equals(s2).ToString());
Can you tell me what I'm doing wrong?
So, == by default for reference types just compares references. Implementing IEquatable<T> does not overload == automatically, you have to do it yourself. string does overload that operator, so it works in Name, but your Name class doesn't, so in Student, the default behaviour is used, which is equivalent to ReferenceEquals().
Implementing IEquatable<T> correctly also means you need to override Equals(object) and GetHashCode(), which you don't do here.
Solution 1 (Recommended)
Since you had implemented IEquatable interface for Name class, just calls .Equals() in Student class.
public class Student : IEquatable<Student>
{
public Name Name { get; }
public Student(Name name) => Name = name;
public bool Equals(Student other)
{
if (ReferenceEquals(this, other))
return true;
if (ReferenceEquals(other, null))
return false;
return Name.Equals(other.Name); // Replaces with .Equals
}
}
Sample .NET Fiddle (Solution 1)
Solution 2
Or you need to implement operator overloading for the == and != operators.
If you use == operator in:
public class Student : IEquatable<Student>
{
...
public bool Equals(Student other)
{
...
return Name == (other.Name);
}
}
public class Name : IEquatable<Name>
{
...
public static bool operator == (Name a, Name b)
{
return a.First == b.First;
}
public static bool operator != (Name a, Name b)
{
return a.First != b.First;
}
}
Sample .NET Fiddle (Solution 2)

How to make Service Fabric Reliable collections case-insensitive?

I have a Stateful Service Fabric service and create, update or read data using IReliableDictionary created with the following code:
var dictionary = await StateManager.GetOrAddAsync<IReliableDictionary<string, Entry>>(ReliableDictionaryName);
// Read
using (ITransaction tx = StateManager.CreateTransaction())
{
ConditionalValue<Entry> result = await dictionary.TryGetValueAsync(tx, name);
return result.HasValue ? result.Value : null;
}
// Create or update
using (ITransaction tx = StateManager.CreateTransaction())
{
await dictionary.AddOrUpdateAsync(tx, entry.Name, entry, (key, prev) => entry);
await tx.CommitAsync();
}
It works, but it is case-sensitive.
Is there any way to make Reliable collection store and get data in a case-insensitive way, except for applying .ToLower() to the keys, which is kind of hacky?
This behavior you see is mostly a property of how strings are compared by default in C#. Reliable dictionaries use a key's implementation of IEquatable and IComparable to perform lookups. If the default behavior of string doesn't work for you, you can implement a type that performs string comparisons the way you want. Then, use the new type as the key for your reliable dictionary. You could implement implicit operators to convert between raw strings and the custom type to make usage painless. Here's an example:
using System.Runtime.Serialization;
[DataContract]
public class CaseInsensitiveString : IEquatable<CaseInsensitiveString>,
IComparable<CaseInsensitiveString>
{
#region Constructors
public CaseInsensitiveString(string value)
{
this.Value = value;
}
#endregion
#region Instance Properties
[DataMember]
public string Value
{
get;
set;
}
#endregion
#region Instance Methods
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 this.Equals((CaseInsensitiveString)obj);
}
public override int GetHashCode()
{
return this.Value != null
? this.Value.GetHashCode()
: 0;
}
public int CompareTo(CaseInsensitiveString other)
{
return string.Compare(this.Value,
other?.Value,
StringComparison.OrdinalIgnoreCase);
}
public bool Equals(CaseInsensitiveString other)
{
if (ReferenceEquals(null,
other))
{
return false;
}
if (ReferenceEquals(this,
other))
{
return true;
}
return string.Equals(this.Value,
other.Value,
StringComparison.OrdinalIgnoreCase);
}
#endregion
#region Class Methods
public static bool operator ==(CaseInsensitiveString left,
CaseInsensitiveString right)
{
return Equals(left,
right);
}
public static implicit operator CaseInsensitiveString(string value)
{
return new CaseInsensitiveString(value);
}
public static implicit operator string(CaseInsensitiveString caseInsensitiveString)
{
return caseInsensitiveString.Value;
}
public static bool operator !=(CaseInsensitiveString left,
CaseInsensitiveString right)
{
return !Equals(left,
right);
}
#endregion
}

C# equality with list-based properties

I've read numerous articles related to proper equality in C#:
http://www.loganfranken.com/blog/687/overriding-equals-in-c-part-1/
What is the best algorithm for an overridden System.Object.GetHashCode?
Assume the following sample class:
public class CustomData
{
public string Name { get; set;}
public IList<double> Values = new List<double>();
}
Would it still be the case to compare the Values property using .Equals()? Here is a full equality sample of what I mean:
#region Equality
public override bool Equals(object value)
{
if(Object.ReferenceEquals(null, value)) return false; // Is null?
if (Object.ReferenceEquals(this, value)) return true; // Is the same object?
if (value.GetType() != this.GetType()) return false; // Is the same type?
return IsEqual((CustomData)value);
}
public bool Equals(CustomData obj)
{
if (Object.ReferenceEquals(null, obj)) return false; // Is null?
if (Object.ReferenceEquals(this, obj)) return true; // Is the same object?
return IsEqual(obj);
}
private bool IsEqual(CustomData obj)
{
return obj is CustomData other
&& other.Name.Equals(Name)
&& other.Values.Equals(Values);
}
public override int GetHashCode()
{
unchecked
{
// Choose large primes to avoid hashing collisions
const int HashingBase = (int) 2166136261;
const int HashingMultiplier = 16777619;
int hash = HashingBase;
hash = (hash * HashingMultiplier) ^ (!Object.ReferenceEquals(null, Name) ? Name.GetHashCode() : 0);
hash = (hash * HashingMultiplier) ^ (!Object.ReferenceEquals(null, Values) ? Values.GetHashCode() : 0);
return hash;
}
}
public static bool operator ==(CustomData obj, CustomData other)
{
if (Object.ReferenceEquals(obj, other)) return true;
if (Object.ReferenceEquals(null, obj)) return false; // Ensure that "obj" isn't null
return (obj.Equals(other));
}
public static bool operator !=(CustomData obj, CustomData other) => !(obj == other);
#endregion
List<T>.Equals(List<T> other) will compare references. If you want equality for property Values to be defined as identical sequences of doubles, use the IEnumerable<TSource>.SequenceEqual.(IEnemerable<TSource> other) method (MSDN). See a refactored version of your IsEqual(CustomData obj) below:
private bool IsEqual(CustomData obj)
{
return obj is CustomData other
&& other.Name.Equals(Name)
&& other.Values.SequenceEqual(Values);
}

Two lists with same elements are not equal. Why?

I have the following:
var a = new List<OrderRule> {
new OrderRule("name", OrderDirection.Ascending),
new OrderRule("age", OrderDirection.Descending)
};
var b = new List<OrderRule> {
new OrderRule("name", OrderDirection.Ascending),
new OrderRule("age", OrderDirection.Descending)
};
var r = a.Equals(b);
The r variable is false even if the two lists include items which are equal to each other. The OrdeRule class implements IEquality. Note that two OrderRules are equal when both Direction and Property are equal.
public enum OrderDirection { ASC, DESC }
public class OrderRule : IEquatable<OrderRule> {
public OrderDirection Direction { get; }
public String Property { get; }
public OrderRule(String property, OrderDirection direction) {
Direction = direction;
Property = property;
}
public Boolean Equals(OrderRule other) {
if (other == null)
return false;
return Property.Equals(other.Property) && Direction.Equals(other.Direction);
}
public override Boolean Equals(Object obj) {
if (ReferenceEquals(null, obj))
return false;
if (ReferenceEquals(this, obj))
return true;
if (obj.GetType() != GetType())
return false;
return Equals(obj as IncludeRule);
}
public override Int32 GetHashCode() {
return HashCode.Of(Property).And(Direction);
}
}
What am I missing?
Checking to see if two lists are equal doesn't mean checking to see if they both contain the same items.
If you do this:
var a = new List<OrderRule>();
var b = new List<OrderRule>();
var r = a.Equals(b); //false
r will always be false, even if the two lists have the same items in them. That's because you're not checking the contents of the lists. You're actually checking to see if both a and b refer to the same object. But a is one object and b is another.
If you did this, r would be true:
var a = new List<OrderRule>();
var b = a;
var r = a.Equals(b) //true
If you have two lists and you want to see if they contain the same items in the same order, you could do this:
var r = a.SequenceEquals(b);
In the example in your question r would be true because you're overriding Equals on OrderRule, so the items in the one list are considered "equal" to the items in the other.
Got it - your equality check in OrderRule is broken.
public override Boolean Equals(Object obj) {
if (ReferenceEquals(null, obj))
return false;
if (ReferenceEquals(this, obj))
return true;
if (obj.GetType() != GetType())
return false;
return Equals(obj as IncludeRule);
}
I think instead of return Equals(obj as IncludeRule) you mean return Equals(obj as OrderRule).
your code is testing to see if they are the same list. not if they have the same contents.
a.Equals(b); // false
var d = a;
d.Equals(a); // true
if you want to compare the contents then use linq's SequenceEqual
https://msdn.microsoft.com/en-us/library/bb348567(v=vs.100).aspx
First whenever one uses new keyword or creates a new object the reference or the address of the object is stored in the variable.
Also equals compares the value of the variable.
Now you have overrided the equals function of the OrderRule. So if you do equals on two OrderRule you will get the result as you compare it inside the overrided equals function.
But now think about this What is List<OrderRule> its nothing but a generic class. So again the equals will check the value of the variable which contains the reference and since they are different you will not get true when you compare them. Also List implements the equals same as that of the object its not overrrided. Hence one way which i prefer is to create an extensions.
public static class Extensions
{
public static bool Equal<T>(this List<T> x, List<T> y)
{
bool isEqual = true;
if (x == null ^ y == null)
{
isEqual = false;
}
else if (x == null && y == null)
{
isEqual = true;
}
else if (x.Equals(y))
{
isEqual = true;
}
else if (x.Count != y.Count)
{
isEqual = false;
}
else
{
//This logic can be changed as per your need.
//Here order of the list matters!
//You can make one where order doesn't matter
for (int i = 0; i < x.Count; i++)
{
if (!x[i].Equals(y[i]))
{
break;
}
}
}
return isEqual;
}
}
Doing so you are comparing references but not objects. So you get inequality returned.

Inline Comparer

I have a class Person with a Name property.
I have a collection of persons.
I have a method to add a new person but I need to check of the collection already contains the person.
I would like to use coll.Contains(newPerson,[here is the comparer]) where the comparer will make the comparison on the name property.
Is it possible to make the comparison inline (anonymously) without creating a new class implementing IEqualityComparer?
In the case you don't want duplicate Person objects, and want to operate on that collection as a set, you can use a HashSet<Person> instead which when calling its Add method will do the check if such a person already exists. For that to work, you can implement IEquatable<Person> in your class. It would look roughly like this:
public class Person : IEquatable<Person>
{
public Person(string name)
{
Name = name;
}
public string Name { get; private set; }
public bool Equals(Person other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase);
}
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((Person) obj);
}
public override int GetHashCode()
{
return (Name != null ? Name.GetHashCode() : 0);
}
public static bool operator ==(Person left, Person right)
{
return Equals(left, right);
}
public static bool operator !=(Person left, Person right)
{
return !Equals(left, right);
}
}
And now you can use it in your HashSet<Person> like this:
void Main()
{
var firstPerson = new Person { Name = "Yuval" };
var secondPerson = new Person { Name = "yuval" };
var personSet = new HashSet<Person> { firstPerson };
Console.WriteLine(personSet.Add(secondPerson)); // Will print false.
}
Note this won't give you the flexibility of multiple comparers, but this way you won't have to create a new class implementing IEqualityComparer<T>.
You can use linq instead.
bool contains = coll.Any(p => p.Name == newPerson.Name);
You can add any condition here as you want. for example as WaiHaLee noted you can make compare ignore case.
bool contains = coll.Any(p => p.Name.Equals(newPerson.Name, StringComparison.OrdinalIgnoreCase));

Categories

Resources