Компаратор
От: G-Host  
Дата: 27.06.11 17:40
Оценка:
Довольно часто приходится передавать IComparer<> в разные методы.
Для пользовательских классов приходится создавать класс, наследовать и т.п., да еще и хранить ссылку на экземпляр отдельно
чтобы то и дело не было создания.
Когда намного проще было бы написать лямбду "на месте".
Написал такой класс:


    public class CustomComparer<T> : IComparer<T>
    {
        private readonly Func<T, T, int> comparer;
        
        private static CustomComparer<T> instance;

        public int Compare(T x, T y)
        {
           return comparer(x, y);
        }

        public CustomComparer(Func<T,T,int> comparer)
        {
            this.comparer = comparer;
        }

        static CustomComparer<T> Create(Func<T,T,int> comparer)
        {
            return instance ?? (instance = new CustomComparer<T>(comparer));
        }
    }


юзать типа так:

CustomComparer<Lalala>.Create((l1, l2) => ...);


Думаю все понятно без комментариев.
Теперь вопрос: тыкните меня в стандартное решение из BCL, свой велосипед конечно самый крутой всегда, но все же я предпочитаю использовать стандартное апи. Или каждый сaм этот лесопед пишет?


28.06.11 19:52: Перенесено модератором из '.NET' — TK
Re: Компаратор
От: Пельмешко Россия blog
Дата: 27.06.11 17:51
Оценка: :)
Здравствуйте, G-Host, Вы писали:

GH>Теперь вопрос: тыкните меня в стандартное решение из BCL, свой велосипед конечно самый крутой всегда, но все же я предпочитаю использовать стандартное апи. Или каждый сaм этот лесопед пишет?


Стандартное решение в BCL для C# — создать тип-компаратор, реализующий интерфейс IComparator<T>.
И это нормально. Нормально для ОО-языка. Это не "лесопед".

Лямбдой вы лишь восполняете отсутствие в C# анонимных определений классов / object expression из F#.
Re[2]: Компаратор
От: Undying Россия  
Дата: 27.06.11 18:06
Оценка: 2 (2) +3
Здравствуйте, Пельмешко, Вы писали:

П>Стандартное решение в BCL для C# — создать тип-компаратор, реализующий интерфейс IComparator<T>.

П>И это нормально. Нормально для ОО-языка. Это не "лесопед".

Логически компаратор это именно функция, т.к. никакого состояния у него нет.

П>Лямбдой вы лишь восполняете отсутствие в C# анонимных определений классов / object expression из F#.


Лямбда это оптимальная форма представления компаратора, полностью соотвествующая его логической сути. А класс компаратора хоть в явном, хоть в анонимном виде это лишний слой абстракции, там где он совершенно не нужен.
Re[3]: Компаратор
От: Пельмешко Россия blog
Дата: 27.06.11 19:00
Оценка:
Здравствуйте, Undying, Вы писали:

П>>Лямбдой вы лишь восполняете отсутствие в C# анонимных определений классов / object expression из F#.


U>Лямбда это оптимальная форма представления компаратора, полностью соотвествующая его логической сути. А класс компаратора хоть в явном, хоть в анонимном виде это лишний слой абстракции, там где он совершенно не нужен.


Знаете, если уж на то пошло, то логическая суть IEnumerator<T>/IObserver<T> тоже прекрасно изображается в виде единственной лямбды, возвращающей/передающей Option<T>, и чего с этого?

Я прекрасно понимаю и согласен с вашей мыслью, лишь хотел подчеркнуть топикстартеру, что .NET в первую очередь всё же объектно-ориентированный framework, а значит определить тип и реализовать интерфейс — нормальная практика, а значит частенько лямбда-рюшечки для старых частей framework'а приходится писать самому
Re[4]: Компаратор
От: samius Япония http://sams-tricks.blogspot.com
Дата: 27.06.11 19:16
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>Я прекрасно понимаю и согласен с вашей мыслью, лишь хотел подчеркнуть топикстартеру, что .NET в первую очередь всё же объектно-ориентированный framework, а значит определить тип и реализовать интерфейс — нормальная практика, а значит частенько лямбда-рюшечки для старых частей framework'а приходится писать самому


Я не знаю исторических причин создания интерфейса IComparer, но думаю, что он родился по аналогии с IEqualityComparer (где нужна связка двух методов Equals и GetHashCode).
В .NET 2.0 внесли и Comparison<T> и Predicate<T> (казалось бы, ничто не мешало ранее иметь обобщенные Comparison и Predicate с параметрами типа object), но удалять IComparer было уже поздно.
А в .NET 3.5 внесли Func<>-и, и стало поздно удалять Comparison<T> и Predicate<T>. Жаль что они несовместимы, и жаль что рюшечки с адаптерами приходится писать самому.
Re[5]: Используемые библиотеки
От: Qbit86 Кипр
Дата: 27.06.11 19:30
Оценка:
Здравствуйте, samius, Вы писали:

S>Жаль что они несовместимы, и жаль что рюшечки с адаптерами приходится писать самому.


Afair, эти адаптеры (обычно называются GenericComparer<T> или как-то так), параметризуемые лямбдами, реализованы в каждой второй библиотеке на правах NIH-велосипеда :) Так что нужно просто покопаться в недрах используемых вами third-party-библиотек где-нибудь в подпространствах Utils/Helpers. Вот, например, в библиотеке C5 подобный компаратор можно найти под названием DelegateComparer<T>.
Глаза у меня добрые, но рубашка — смирительная!
Re[6]: Используемые библиотеки
От: samius Япония http://sams-tricks.blogspot.com
Дата: 27.06.11 19:35
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Здравствуйте, samius, Вы писали:


S>>Жаль что они несовместимы, и жаль что рюшечки с адаптерами приходится писать самому.


Q>Afair, эти адаптеры (обычно называются GenericComparer<T> или как-то так), параметризуемые лямбдами, реализованы в каждой второй библиотеке на правах NIH-велосипеда Так что нужно просто покопаться в недрах используемых вами third-party-библиотек где-нибудь в подпространствах Utils/Helpers. Вот, например, в библиотеке C5 подобный компаратор можно найти под названием DelegateComparer<T>.


Обычно я не копаюсь даже в своих исходниках дальше текущего проекта. Дешевле написать влет. Что-нибудь вроде Flatten с неагрессивным использованием стека — уже можно поискать.
Re[4]: Компаратор
От: Undying Россия  
Дата: 27.06.11 19:38
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>Я прекрасно понимаю и согласен с вашей мыслью, лишь хотел подчеркнуть топикстартеру, что .NET в первую очередь всё же объектно-ориентированный framework,


Нет никакого противопоставления между объектами и функциями. Соответственно представление всего объектами это такая же глупость, как представление всего функциями.
Re: Компаратор
От: Аlexey  
Дата: 27.06.11 20:58
Оценка: +2
Здравствуйте, G-Host, Вы писали:

GH>
GH>    public class CustomComparer<T> : IComparer<T>
GH>    {
GH>        private readonly Func<T, T, int> comparer;
        
GH>        private static CustomComparer<T> instance;

GH>        public int Compare(T x, T y)
GH>        {
GH>           return comparer(x, y);
GH>        }

GH>        public CustomComparer(Func<T,T,int> comparer)
GH>        {
GH>            this.comparer = comparer;
GH>        }

GH>        static CustomComparer<T> Create(Func<T,T,int> comparer)
GH>        {
GH>            return instance ?? (instance = new CustomComparer<T>(comparer));
GH>        }
GH>    }
GH>


GH>юзать типа так:


GH>
GH>CustomComparer<Lalala>.Create((l1, l2) => ...);
GH>


Немного покритикую реализацию.
Кеширование экземпляра в статическом поле кажется очень подозрительным...
Сегодня вы написали код:
CustomComparer<SomeClass>.Create((x, y) => Comparer<int>.Default.Compare(x.FieldInt, y.FieldInt));

А завтра ваш коллега решит воспользоваться вашим классов и напишет в другом месте:
CustomComparer<SomeClass>.Create((x, y) => Comparer<string>.Default.Compare(x.FieldStr, y.FieldStr));

И в результате вы начинаете искать почему же сравнение выполняется не так как ожидалось
Re[3]: Компаратор
От: Tissot Россия  
Дата: 27.06.11 21:01
Оценка: -1
Здравствуйте, Undying, Вы писали:

U>Логически компаратор это именно функция, т.к. никакого состояния у него нет.


А кто вам сказал, что у функции обязательно нет состояния?
Re: Компаратор
От: _FRED_ Черногория
Дата: 27.06.11 21:22
Оценка: 8 (2) +1
Здравствуйте, G-Host, Вы писали:

GH>Довольно часто приходится передавать IComparer<> в разные методы.

GH>Для пользовательских классов приходится создавать класс, наследовать и т.п., да еще и хранить ссылку на экземпляр отдельно
GH>чтобы то и дело не было создания.
GH>Когда намного проще было бы написать лямбду "на месте".

Был такой замечательный советский фильм про конструкторов ракет (начинавших ещё в довоенные годы). Так там вот один такой "молодой" конструктор приходит к двум уже "заматеревшим" и показывает свои чертежи. А те смотрят и спрашивают: "Шестой вариант уже поди?" — "а откуда вы знаете" — "А вот когда тридцатый (тут точную цифру забыл я) сделаешь, сам поймёшь"

Далее сначала добавляется ещё один (Convert) "самый полезный метод":

  public static partial class Comparers
  {
    public static Comparer<T> Create<T>(Comparison<T> comparison) {
      Argument.NotNull(comparison, "comparison");
      return new ComparisonComparer<T>(comparison);
    }

    public static Comparer<TOutput> Convert<TInput, TOutput>(this IComparer<TInput> comparer, Converter<TOutput, TInput> converter) {
      Argument.NotNull(comparer, "comparer");
      Argument.NotNull(converter, "converter");

      Comparison<TOutput> comparison = (x, y) => comparer.Compare(converter(x), converter(y));
      return Create(comparison);
    }

    [Serializable]
    private sealed class ComparisonComparer<T> : Comparer<T>
    {
      public ComparisonComparer(Comparison<T> comparison) {
        Argument.NotNull(comparison, "comparison");
        Comparison = comparison;
      }

      private Comparison<T> Comparison { get; set; }

      [ContractInvariantMethod]
      private void Invariant() {
        Contract.Invariant(Comparison != null);
      }

      public override int Compare(T x, T y) {
        return Comparison(x, y);
      }
    }
  }
  Потом другой:
  public static partial class Comparers
  {
    public static Comparer<T> Reverse<T>(this IComparer<T> comparer) {
      Argument.NotNull(comparer, "comparer");
      return new ReverseComparer<T>(comparer);
    }

    public static Comparer<T> DefaultReverse<T>() {
      return ReverseComparer<T>.Default;
    }

    [Serializable]
    private sealed class ReverseComparer<T> : Comparer<T>
    {
      private static readonly ReverseComparer<T> @default = new ReverseComparer<T>(Comparer<T>.Default);

      public ReverseComparer(IComparer<T> comparer) {
        Argument.NotNull(comparer, "comparer");
        Original = comparer;
      }

      public static new ReverseComparer<T> Default {
        [DebuggerStepThrough]
        get { return @default; }
      }

      private IComparer<T> Original { get; set; }

      [ContractInvariantMethod]
      private void Invariant() {
        Contract.Invariant(Original != null);
      }

      public override int Compare(T x, T y) {
        return Original.Compare(y, x);
      }
    }
  }
  Третий…
  public static partial class Comparers
  {
    public static Comparer<T> ThenBy<T>(this IComparer<T> first, IComparer<T> second) {
      Argument.NotNull(first, "first");
      Argument.NotNull(second, "second");

      return new ThenByComparer<T>(first, second);
    }

    [Serializable]
    private sealed class ThenByComparer<T> : Comparer<T>
    {
      public ThenByComparer(IComparer<T> first, IComparer<T> second) {
        Argument.NotNull(first, "first");
        Argument.NotNull(second, "second");

        First = first;
        Second = second;
      }

      private IComparer<T> First { get; set; }
      private IComparer<T> Second { get; set; }

      [ContractInvariantMethod]
      private void Invariant() {
        Contract.Invariant(First != null);
        Contract.Invariant(Second != null);
      }

      public override int Compare(T x, T y) {
        var value = First.Compare(x, y);
        if(value == 0) {
          return Second.Compare(x, y);
        }//if
        return value;
      }
    }
  }
  Ну и до кучи
  public static partial class Comparers
  {
    public static bool Equals<T>(this IComparer<T> comparer, T left, T right) {
      Argument.NotNull(comparer, "comparer");
      return comparer.Compare(left, right) == 0;
    }

    public static bool NotEquals<T>(this IComparer<T> comparer, T left, T right) {
      Argument.NotNull(comparer, "comparer");
      return comparer.Compare(left, right) != 0;
    }

    public static bool LessThan<T>(this IComparer<T> comparer, T left, T right) {
      Argument.NotNull(comparer, "comparer");
      return comparer.Compare(left, right) < 0;
    }

    public static bool LessThanOrEqual<T>(this IComparer<T> comparer, T left, T right) {
      Argument.NotNull(comparer, "comparer");
      return comparer.Compare(left, right) <= 0;
    }

    public static bool GreaterThan<T>(this IComparer<T> comparer, T left, T right) {
      Argument.NotNull(comparer, "comparer");
      return comparer.Compare(left, right) > 0;
    }

    public static bool GreaterThanOrEqual<T>(this IComparer<T> comparer, T left, T right) {
      Argument.NotNull(comparer, "comparer");
      return comparer.Compare(left, right) >= 0;
    }
  }

А остальное уж совсем экзотика

Тут полезно что: наружу выдаётся не IComparer<>, а Comaprer<>, качественное отличие которого в том, что он так же реализует и не-дженерик IComparer и его можно использовать в больше количестве сценариев и даётся это совершенно забесплатно.

Метод Convert позволяет отсортировать, например, коллекцию бизнес-объектов по какому-либо полю, а ThenBy по совокупности полей.
Help will always be given at Hogwarts to those who ask for it.
Re[2]: Компаратор
От: G-Host  
Дата: 27.06.11 21:23
Оценка:
Здравствуйте, Аlexey, Вы писали:

А>Немного покритикую реализацию.

А>Кеширование экземпляра в статическом поле кажется очень подозрительным...
А>Сегодня вы написали код:
А>
А>CustomComparer<SomeClass>.Create((x, y) => Comparer<int>.Default.Compare(x.FieldInt, y.FieldInt));
А>

А>А завтра ваш коллега решит воспользоваться вашим классов и напишет в другом месте:
А>
А>CustomComparer<SomeClass>.Create((x, y) => Comparer<string>.Default.Compare(x.FieldStr, y.FieldStr));
А>

А>И в результате вы начинаете искать почему же сравнение выполняется не так как ожидалось

Да, я знаю про такой вариант. Потому есть
public CustomComparer(Func<T,T,int> comparer)
и я использую статический экземпляр только когда наверняка уверен что он нужен в одном месте

Да и, улучшить же можно сопоставлением функция-объект.
Re[2]: Компаратор
От: Nikolay_P_I  
Дата: 28.06.11 04:12
Оценка:
Здравствуйте, _FRED_, Вы писали:

А можно поделиться окончательным результатом для использования сторонними разработчиками не разжигая аппетит ? Пожалуйста
Re[4]: Компаратор
От: Tissot Россия  
Дата: 28.06.11 07:14
Оценка:
Здравствуйте, Tissot, Вы писали:

U>>Логически компаратор это именно функция, т.к. никакого состояния у него нет.


T>А кто вам сказал, что у функции обязательно нет состояния?


С чем несогласен drol?
Re[4]: Компаратор
От: Undying Россия  
Дата: 28.06.11 07:46
Оценка:
Здравствуйте, Tissot, Вы писали:

U>>Логически компаратор это именно функция, т.к. никакого состояния у него нет.

T>А кто вам сказал, что у функции обязательно нет состояния?

Ты умеешь использовать функцию для хранения состояния? Можно код продемонстрировать, в котором функция это делает?
Re[5]: Замыкание
От: Qbit86 Кипр
Дата: 28.06.11 07:48
Оценка: +2
Здравствуйте, Undying, Вы писали:

U>Ты умеешь использовать функцию для хранения состояния? Можно код продемонстрировать, в котором функция это делает?


Замыкание?
Глаза у меня добрые, но рубашка — смирительная!
Re[5]: Компаратор
От: Tissot Россия  
Дата: 28.06.11 08:02
Оценка:
Здравствуйте, Undying, Вы писали:

U>>>Логически компаратор это именно функция, т.к. никакого состояния у него нет.

T>>А кто вам сказал, что у функции обязательно нет состояния?

U>Ты умеешь использовать функцию для хранения состояния? Можно код продемонстрировать, в котором функция это делает?


Нет, я не использую функцию для хранения состояния, я написал, что она может его иметь. Погугли по memoize, найдешь пример функции с "состоянием".
Re[6]: Замыкание
От: Undying Россия  
Дата: 28.06.11 08:11
Оценка:
Здравствуйте, Qbit86, Вы писали:

U>>Ты умеешь использовать функцию для хранения состояния? Можно код продемонстрировать, в котором функция это делает?

Q>Замыкание?

Там же не в функции состояние хранится, а в переменной объявленной вне функции.
Re[7]: Замыкание
От: Tissot Россия  
Дата: 28.06.11 22:44
Оценка:
Здравствуйте, Undying, Вы писали:

U>>>Ты умеешь использовать функцию для хранения состояния? Можно код продемонстрировать, в котором функция это делает?

Q>>Замыкание?

U>Там же не в функции состояние хранится, а в переменной объявленной вне функции.


Офигеть аргументация. Тогда и у объектов состояния нет, в состояние же не в них хранится, а в полях.
Re[3]: Компаратор
От: _FRED_ Черногория
Дата: 05.07.11 05:50
Оценка:
Здравствуйте, Nikolay_P_I, Вы писали:

N_P>А можно поделиться окончательным результатом для использования сторонними разработчиками не разжигая аппетит ? Пожалуйста


А там уже ничего интересного и не осталось. Create/Convert для IEqualityComparer<>:
  Скрытый текст
  public static partial class Comparers
  {
    public static EqualityComparer<T> Create<T>(Func<T, T, bool> comparison, Func<T, int> hashCode) {
      Argument.NotNull(comparison, "comparison");
      Argument.NotNull(hashCode, "hashCode");
      return new ComparisonEqualityComparer<T>(comparison, hashCode);
    }

    public static EqualityComparer<T> Create<T>(Func<T, T, bool> equals) {
      Argument.NotNull(equals, "equals");
      return Create(equals, EqualityComparer<T>.Default.GetHashCode);
    }

    public static EqualityComparer<TOutput> Convert<TInput, TOutput>(this IEqualityComparer<TInput> comparer, Converter<TOutput, TInput> converter) {
      Argument.NotNull(comparer, "comparer");
      Argument.NotNull(converter, "converter");
      Func<TOutput, TOutput, bool> comparison = (x, y) => comparer.Equals(converter(x), converter(y));
      Func<TOutput, int> hashCode = x => comparer.GetHashCode(converter(x));
      return Create(comparison, hashCode);
    }

    [Serializable]
    private sealed class ComparisonEqualityComparer<T> : EqualityComparer<T>
    {
      public ComparisonEqualityComparer(Func<T, T, bool> comparison, Func<T, int> hashCode) {
        Argument.NotNull(comparison, "comparison");
        Argument.NotNull(hashCode, "hashCode");

        Comparison = comparison;
        HashCode = hashCode;
      }

      private Func<T, T, bool> Comparison{get;set;}
      private Func<T, int> HashCode {get;set;}

      [ContractInvariantMethod]
      private void Invariant() {
        Contract.Invariant(Comparison != null);
        Contract.Invariant(HashCode != null);
      }

      public override bool Equals(T x, T y) {
        return Comparison(x, y);
      }

      public override int GetHashCode(T obj) {
        return HashCode(obj);
      }
    }
  }


И преобразование интерфейсов компараторов (IEqualityComparer<T>, IEqualityComparer) в универсальный (EqualityComparer<T>)
  Скрытый текст
  public static partial class Comparers
  {
    public static EqualityComparer<T> ToTyped<T>(this IEqualityComparer<T> comparer) {
      Argument.NotNull(comparer, "comparer");
      return comparer as EqualityComparer<T> ?? new TypedEqualityComparer<T>(comparer);
    }

    public static EqualityComparer<T> ToTyped<T>(this IEqualityComparer comparer) {
      Argument.NotNull(comparer, "comparer");
      return comparer as EqualityComparer<T> ?? new UntypedEqualityComparer<T>(comparer);
    }

    [Serializable]
    private sealed class TypedEqualityComparer<T> : EqualityComparer<T>
    {
      public TypedEqualityComparer(IEqualityComparer<T> comparer) {
        Argument.NotNull(comparer, "comparer");
        Comparer = comparer;
      }

      private IEqualityComparer<T> Comparer { get; set; }

      [ContractInvariantMethod]
      private void Invariant() {
        Contract.Invariant(Comparer != null);
      }

      public override bool Equals(T x, T y) {
        return Comparer.Equals(x, y);
      }

      public override int GetHashCode(T obj) {
        return Comparer.GetHashCode(obj);
      }
    }

    [Serializable]
    private sealed class UntypedEqualityComparer<T> : EqualityComparer<T>
    {
      public UntypedEqualityComparer(IEqualityComparer comparer) {
        Argument.NotNull(comparer, "comparer");
        Comparer = comparer;
      }

      private IEqualityComparer Comparer { get; set; }

      [ContractInvariantMethod]
      private void Invariant() {
        Contract.Invariant(Comparer != null);
      }

      public override bool Equals(T x, T y) {
        return Comparer.Equals(x, y);
      }

      public override int GetHashCode(T obj) {
        Argument.NotNull(obj, "obj");
        return Comparer.GetHashCode(obj);
      }
    }
  }


  public static partial class Comparers
  {
    public static Comparer<T> ToTyped<T>(this IComparer<T> comparer) {
      Argument.NotNull(comparer, "comparer");
      return comparer as Comparer<T> ?? new TypedComparer<T>(comparer);
    }

    public static Comparer<T> ToTyped<T>(this IComparer comparer) {
      Argument.NotNull(comparer, "comparer");
      return comparer as Comparer<T> ?? new UntypedComparer<T>(comparer);
    }

    [Serializable]
    private sealed class TypedComparer<T> : Comparer<T>
    {
      public TypedComparer(IComparer<T> comparer) {
        Argument.NotNull(comparer, "comparer");
        Comparer = comparer;
      }

      private IComparer<T> Comparer { get; set; }

      [ContractInvariantMethod]
      private void Invariant() {
        Contract.Invariant(Comparer != null);
      }

      public override int Compare(T x, T y) {
        return Comparer.Compare(x, y);
      }
    }

    [Serializable]
    private sealed class UntypedComparer<T> : Comparer<T>
    {
      public UntypedComparer(IComparer comparer) {
        Argument.NotNull(comparer, "comparer");
        Comparer = comparer;
      }

      private IComparer Comparer { get; set; }

      [ContractInvariantMethod]
      private void Invariant() {
        Contract.Invariant(Comparer != null);
      }

      public override int Compare(T x, T y) {
        return Comparer.Compare(x, y);
      }
    }
  }
Help will always be given at Hogwarts to those who ask for it.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.