Мне кажется, что то, что вы пытаетесь достичь, очень похоже на цепочку ответственности .
Так почему бы вам не расположить всех своих сравнителей Foo вцепочечная структура и делает цепочку расширяемой, чтобы новые ссылки могли быть добавлены во время выполнения?
Вот идея:
клиент будет реализовывать любые средства сравнения Foo, которые он хочет, и всеони будут аккуратно организованы таким образом, что все они будут вызываться один за другим, и если кто-то вернет false, тогда все сравнение возвращает false!
Вот код:
public abstract class FooComparer
{
private readonly FooComparer _next;
public FooComparer(FooComparer next)
{
_next = next;
}
public bool CompareFoo(Foo a, Foo b)
{
return AreFoosEqual(a, b)
&& (_next?.CompareFoo(a, b) ?? true);
}
protected abstract bool AreFoosEqual(Foo a, Foo b);
}
public class FooNameComparer : FooComparer
{
public FooNameComparer(FooComparer next) : base(next)
{
}
protected override bool AreFoosEqual(Foo a, Foo b)
{
return a.Name == b.Name;
}
}
public class FooBarComparer : FooComparer
{
public FooBarComparer(FooComparer next) : base(next)
{
}
protected override bool AreFoosEqual(Foo a, Foo b)
{
return a.Bar == b.Bar;
}
}
идея абстрактного класса FooComparer
заключается в том, чтобы иметь что-то вроде менеджера цепочки;он обрабатывает вызов всей цепочки и вынуждает его производные классы реализовывать код для сравнения Foo, все время выставляя метод CompareFoo
, который будет использовать клиент.
И как клиент будет использоватьЭто?ну, он может сделать что-то вроде этого:
var manager = new FooManager();
manager.FooComparer
= new FooNameComparer(new FooBarComparer(null));
manager.FooComparer.CompareFoo(fooA, fooB);
Но круче, если они могут зарегистрировать цепочку FooComparer в контейнере IoC!
Редактировать
Это более упрощенный подход, который я использовал некоторое время для пользовательского сравнения вещей:
public class GenericComparer<T> : IEqualityComparer<T> where T : class
{
private readonly Func<T, object> _identitySelector;
public GenericComparer(Func<T, object> identitySelector)
{
_identitySelector = identitySelector;
}
public bool Equals(T x, T y)
{
var first = _identitySelector.Invoke(x);
var second = _identitySelector.Invoke(y);
return first != null && first.Equals(second);
}
public int GetHashCode(T obj)
{
return _identitySelector.Invoke(obj).GetHashCode();
}
}
public bool CompareFoo2(Foo a, Foo b, params IEqualityComparer<Foo>[] comparers)
{
foreach (var comparer in comparers)
{
if (!comparer.Equals(a, b))
{
return false;
}
}
return true;
}
И пусть клиент делает:
var areFoosEqual = CompareFoo2(a, b,
new GenericComparer<Foo>(foo => foo.Name),
new GenericComparer<Foo>(foo => foo.Bar))
Возможно адаптироватьGenericComparer должен иметь несколько селекторов идентификаторов, чтобы передавать их все в одну лямбду, но нам также необходимо обновить его GetHashCode
метод для правильного вычисления HashCode, используя все объекты идентификаторов.