C# 9 introduce registros que, entre otros beneficios, permiten una comparación realmente sencilla. ¿Hay alguna forma en que pueda aprovechar esa funcionalidad para comparar un registro compuesto por una colección de otros registros?
Por ejemplo:
record Foo { string Name {get; set;} List<Bar> Bars {get; set;} public Foo(string name, params int[] values) { Name = name; Bars = values.Select(v => new Bar(v)).ToList(); } } record Bar { int Value {get; set;} public Bar(int value) => Value = value; }En otro lugar del código:
var foo1 = new Foo("Hi",1,2,3); var foo2 = new Foo("Hi",1,2,3); return foo1 == foo2; // I want this to return true Por cierto, no estoy buscando una solución para este código específico. Sé que puedo anular el operador == o implementar IComparable<Foo> , etc. Mi objetivo es aprovechar la funcionalidad integrada para no tener que implementar mis propios métodos cada vez que quiero comparar un contenedor de datos compuesto por una colección de contenedores de datos. ¿Hay una manera de hacer eso?
¡Gracias!
Desafortunadamente, no hay una forma "agradable" de hacer lo que quieres. Por lo general, tiene una de dos opciones:
SequenceEqual para la lista, oPara lo último, puede escribir una clase contenedora para la lista, por ejemplo, como se describe en esta respuesta a "tipos de registro con propiedades de colección y colecciones con semántica de valor" , y luego solo use esa clase en sus registros.
De hecho, encontré una buena solución. Puede extender List<T> para anular Equals y GetHashCode
public class ValueEqualityList<T>:List<T> { private readonly bool _requireMathcingOrder; public ValueEqualityList(bool requireMatchingOrder = false) => _requireMathcingOrder = requireMatchingOrder; public override bool Equals(object other) { if (!(other is IEnumerable<T> enumerable)) return false; if(!_requireMathcingOrder)return enumerable.ScrambledEquals(this); return enumerable.SequenceEqual(this); } public override int GetHashCode() { var hashCode = 0; foreach (var item in this) { hashCode ^= item.GetHashCode(); } return hashCode; } } Foo se convierte en:
record Foo { string Name {get; set;} List<Bar> Bars {get; set;} public Foo(string name, params int[] values) { Name = name; //this is the line that changed Bars = new ValueEqualityList<Bar>(values.Select(v => new Bar(v))); } }Esto usa algún código de ayuda:
static class EnumerableExtensions { /// <summary> /// Returns true if both enumerables contain the same items, regardless of order. O(N*Log(N)) /// </summary> /// <typeparam name="T"></typeparam> /// <param name="first"></param> /// <param name="second"></param> /// <returns></returns> public static bool ScrambledEquals<T>(this IEnumerable<T> first, IEnumerable<T> second) { var counts = first.GetCounts(); foreach (var item in second) { if (!counts.TryGetValue(item, out var count)) return false; count -= 1; counts[item] = count; if (count < 0) return false; } return counts.Values.All(c => c == 0); } public static Dictionary<T, int> GetCounts<T>(this IEnumerable<T> enumerable) { var counts = new Dictionary<T, int>(); foreach (var item in enumerable) { if (!counts.TryGetValue(item, out var count)) { count = 0; } count++; counts[item] = count; } return counts; } }