Equ
Equ copied to clipboard
Equ all the way down
Hi,
I encountered unexpected behaviour in Equ wherein the following (uncommented) unit test failed:
namespace Equ.Test
{
public class NestedCollectionsTest
{
public class Level2
{
public string BasicProperty { get; set; }
}
public class Level1
{
public ICollection<Level2> Items { get; set; }
}
public class Container
{
public ICollection<Level1> Items { get; set; }
}
public static IEnumerable<object[]> ShouldDetermineCorrectEqualityTests => new List<object[]>
{
//new object[] { new Container(), new Container(), true },
//new object[] {
// new Container { Items = new List<Level1>() },
// new Container { },
// false
//},
//new object[] {
// new Container { Items = new List<Level1>() },
// new Container { Items = new List<Level1>() },
// true
//},
//new object[] {
// new Container { Items = new List<Level1>() },
// new Container { Items = Array.Empty<Level1>() },
// true
//},
//new object[] {
// new Container { Items = new [] { new Level1() } },
// new Container { Items = Array.Empty<Level1>() },
// false
//},
new object[] {
new Container { Items = new [] { new Level1() } },
new Container { Items = new [] { new Level1() } },
true
},
};
[Theory]
[MemberData(nameof(ShouldDetermineCorrectEqualityTests))]
public void ShouldDetermineCorrectEquality(Container x, Container y, bool expected)
{
Assert.Equal(expected, MemberwiseEqualityComparer<Container>.ByProperties.Equals(x, y));
}
}
}
It turns out that, while properties were being used for equality comparision at the root level, collections properties were falling back to using Enumerable.SequenceEqual
to determine equality. This caused the above test to fail as the Level1
objects were different instances despite the fact that MemberwiseEqualityConverter<Level1>.ByProperties.Equals
would have returned true if used to compare them.
This PR introduces new ByFieldsRecursive
and ByPropertiesRecursive
properties to MemberwiseEqualityComparer<T>
which (via an additional MemberwiseEqualityMode
parameter on EqualityFunctionGenerator
) cause ElementwiseSequenceEqualityComparer<T>
to use the appropriate MemberwiseEqualityComparer<T>
instance to compare internal collections such that the following unit tests pass:
public class NestedCollectionsTest
{
public class Level2
{
public string BasicProperty { get; set; }
}
public class Level1
{
public ICollection<Level2> Items { get; set; }
}
public class Container
{
public ICollection<Level1> Items { get; set; }
}
public static IEnumerable<object[]> ShouldDetermineCorrectEqualityTests => new List<object[]>
{
new object[] { new Container(), new Container(), MemberwiseEqualityComparer<Container>.ByProperties, true },
new object[] {
new Container { Items = new List<Level1>() },
new Container { },
MemberwiseEqualityComparer<Container>.ByProperties,
false
},
new object[] {
new Container { Items = new List<Level1>() },
new Container { Items = new List<Level1>() },
MemberwiseEqualityComparer<Container>.ByProperties,
true
},
new object[] {
new Container { Items = new List<Level1>() },
new Container { Items = Array.Empty<Level1>() },
MemberwiseEqualityComparer<Container>.ByProperties,
true
},
new object[] {
new Container { Items = new [] { new Level1() } },
new Container { Items = Array.Empty<Level1>() },
MemberwiseEqualityComparer<Container>.ByProperties,
false
},
new object[] {
new Container { Items = new [] { new Level1() } },
new Container { Items = new [] { new Level1() } },
MemberwiseEqualityComparer<Container>.ByProperties,
false
},
new object[] {
new Container { Items = new [] { new Level1() } },
new Container { Items = new [] { new Level1() } },
MemberwiseEqualityComparer<Container>.ByPropertiesRecursive,
true
},
new object[] {
new Container { Items = new [] { new Level1 { Items = new[] { new Level2() } } } },
new Container { Items = new [] { new Level1 { Items = new[] { new Level2() } } } },
MemberwiseEqualityComparer<Container>.ByProperties,
false
},
new object[] {
new Container { Items = new [] { new Level1 { Items = new[] { new Level2() } } } },
new Container { Items = new [] { new Level1 { Items = new[] { new Level2() } } } },
MemberwiseEqualityComparer<Container>.ByPropertiesRecursive,
true
},
};
[Theory]
[MemberData(nameof(ShouldDetermineCorrectEqualityTests))]
public void ShouldDetermineCorrectEquality(Container x, Container y, MemberwiseEqualityComparer<Container> equalityComparer, bool expected)
{
Assert.Equal(expected, equalityComparer.Equals(x, y));
}
}
I hope you will find this PR useful and worth merging into the Equ package. If, for some reason, you don't feel it's appropriate to add this functionality to the package, could you let me know so that I can build my own package encompasing it.
Thanks, Ian
This seems like a reasonable and useful change to me.
Amazingly I hadn't realised Equ doesn't provided deep equality for nested objects either.
Since @ruler501's last review I've added an additional commit which allows MemberwiseEqualityComparer<T>.ByFieldRecursive
& MemberwiseEqualityComparer<T>.ByPropertyRecursive
to use MemberwiseEqualityComparer for nested objects instead of Object.Equals.
Needs documentation of the new features.