As a result of #1, I added some logic that changed how hash code and equality checks were done for types that implement IEnumerable. They are as follows:
While these are reasonable defaults, there is no guarantee a user wishes this behavior to happen all of the time. For example, a property of type SortedSet<T> may care about order, despite implementing ISet<T>, or a property may be of a type that implements IEnumerable, but the instead should use the standard equality and hash code generation implementations. We need to support these one-off cases to override the default behavior in ImmutableBase<TImmutable>.
Task
Update ImmutableBase<TImmutable> to look for and respect an [EnumerableComparison] like attribute that can be applied to types of IEnumerable, letting the comparison logic know how that property is meant to have its hash code and equality calculated. For example, it could look like this:
public class CustomEnumerable : ImmutableBase<CustomEnumerable>, IEnumerable {
public String Key { get; }
public IEnumerable Values { get; }
public CustomEnumerable(String key, IEnumerable values) {
if (values == null) {
throw new ArgumentNullException(nameof(values));
}
this.Key = key;
this.Values = values;
}
public IEnumerator GetEnumerator() {
return this.Values.GetEnumerator();
}
}
public class MyImmutable : ImmutableBase<MyImmutable> {
[EnumerableComparisonAttribute(EnumerableComparison.AsSet)]
public IEnumerable<Guid> Ids { get; }
[EnumerableComparisonAttribute(EnumerableComparison.Default)]
public CustomEnumerable Custom { get; }
public MyImmutable(ISet<Guid> ids, CustomEnumerable custom) {
this.Ids = ids;
this.Custom = custom;
}
}
public class MyImmutableTests {
[Fact]
public void InequalityForIds() {
// Arrange
var idsOne = new [] { Guid.NewGuid(), Guid.NewGuid() };
var idsTwo = new [] { idsOne[1], idsOne[0] };
var immutableOne = new MyImmutable(idsOne, null);
var immutableTwo = new MyImmutable(idsTwo, null);
// Act
var areEqual = immutableOne.Equals(immutableTwo);
// Assert
Assert.True(
areEqual,
"This would be 'false' with current logic, " +
"since the default comparison for IEnumerable considers order."
);
}
[Fact]
public void InequalityForCustomEnumerable() {
// Arrange
var customOne = new CustomEnumerable("one", new [] { "foo", "bar" });
var customTwo = new CustomEnumerable("two", new [] { "foo", "bar" });
var immutableOne = new MyImmutable(new [] { Guid.NewGuid() }, customOne);
var immutableTwo = new MyImmutable(new [] { Guid.NewGuid() }, customTwo);
// Act
var areEqual = immutableOne.Equals(immutableTwo);
// Assert
Assert.False(
areEqual,
"This would be 'true' with current logic, " +
"since CustomEnumerable implements IEnumerable."
);
}
}
Note
If the application of an [EnumerableComparison] attribute changes an IEnumerable implementation to instead use the standard hash code and equality logic, the ToString() logic referenced in #13 should respect this as well.
Background
As a result of #1, I added some logic that changed how hash code and equality checks were done for types that implement
IEnumerable
. They are as follows:ISet<T>
, we perform an order agnostic comparison of all items in the collection.IEnumerable
and is not a string, we perform an order-specific comparison of all items in the collection.String
use the default String comparison ofOrdinal
.While these are reasonable defaults, there is no guarantee a user wishes this behavior to happen all of the time. For example, a property of type
SortedSet<T>
may care about order, despite implementingISet<T>
, or a property may be of a type that implementsIEnumerable
, but the instead should use the standard equality and hash code generation implementations. We need to support these one-off cases to override the default behavior inImmutableBase<TImmutable>
.Task
Update
ImmutableBase<TImmutable>
to look for and respect an[EnumerableComparison]
like attribute that can be applied to types ofIEnumerable
, letting the comparison logic know how that property is meant to have its hash code and equality calculated. For example, it could look like this:Note
If the application of an
[EnumerableComparison]
attribute changes anIEnumerable
implementation to instead use the standard hash code and equality logic, theToString()
logic referenced in #13 should respect this as well.