У меня есть несколько общих функций равенства, которые используются при переопределении Object.Equals
:
type IEqualityComparer<'T> = System.Collections.Generic.IEqualityComparer<'T>
let equalIf f (x:'T) (y:obj) =
if obj.ReferenceEquals(x, y) then true
else
match box x, y with
| null, _ | _, null -> false
| _, (:? 'T as y) -> f x y
| _ -> false
let equalByWithComparer (comparer:IEqualityComparer<_>) f (x:'T) (y:obj) =
(x, y) ||> equalIf (fun x y -> comparer.Equals(f x, f y))
Типичное использование будет:
type A(name) =
member __.Name = name
override this.Equals(that) =
(this, that) ||> equalByWithComparer StringComparer.InvariantCultureIgnoreCase (fun a -> a.Name)
type B(parent:A, name) =
member __.Parent = parent
member __.Name = name
override this.Equals(that) = (this, that) ||> equalIf (fun x y ->
x.Parent.Equals(y.Parent) && StringComparer.InvariantCultureIgnoreCase.Equals(x.Name, y.Name))
Я в основном доволен этим.Это уменьшает шаблон [wikipedia] .Но меня раздражает необходимость использовать equalBy
вместо более краткого equalByWithComparer
в типе B
(так как его равенство зависит от родительского).
Такое ощущение, что должна быть возможность написать функцию, которая принимает ссылку на родителя (или проекции 0..N), которые проверяются на равенство с использованием Equals
, вместе со свойством, которое нужно проверятьи сопутствующий компаратор, но я пока не смог представить его реализацию.Возможно, все это перестаралось (не уверен).Как может быть реализована такая функция?
РЕДАКТИРОВАТЬ
Основываясь на ответе Брайана, я пришел с этим, который, кажется, работает нормально.
let equalByProjection proj (comparer:IEqualityComparer<_>) f (x:'T) (y:obj) =
(x, y) ||> equalIf (fun x y ->
Seq.zip (proj x) (proj y)
|> Seq.forall obj.Equals && comparer.Equals(f x, f y))
type B(parent:A, otherType, name) =
member __.Parent = parent
member __.OtherType = otherType //Equals is overridden
member __.Name = name
override this.Equals(that) =
(this, that) ||> equalByProjection
(fun x -> [box x.Parent; box x.OtherType])
StringComparer.InvariantCultureIgnoreCase (fun b -> b.Name)