C#: как реализовать IOrderedEnumerable‹T›

Я хочу реализовать несколько различных алгоритмов для практики, просто чтобы увидеть, насколько я плох на самом деле, и стать лучше :p

В любом случае, я подумал, что попробую использовать IEnumerable<T> и IOrderedEnumerable<T> и другие типы коллекций .Net просто для совместимости (чтобы то, что я пишу, можно было легче использовать позже).

Но я не могу найти способ вернуть экземпляр IOrderedEnumerable<T>, кроме как с помощью методов расширения OrderBy и ThenBy. Поэтому я думаю, мне нужно создать свой собственный класс, реализующий этот интерфейс. Но интерфейс мне не совсем понятен, если честно. Может быть, но я не уверен.

Я создал пустой класс, добавил интерфейс, а затем попросил ReSharper добавить для меня пустые реализации. Это выглядит так:

class MyOrderedEnumerable<T> : IOrderedEnumerable<T>
{
    /// <summary>
    /// Performs a subsequent ordering on the elements of an <see cref="T:System.Linq.IOrderedEnumerable`1"/> according to a key.
    /// </summary>
    /// <returns>
    /// An <see cref="T:System.Linq.IOrderedEnumerable`1"/> whose elements are sorted according to a key.
    /// </returns>
    /// <param name="keySelector">The <see cref="T:System.Func`2"/> used to extract the key for each element.</param><param name="comparer">The <see cref="T:System.Collections.Generic.IComparer`1"/> used to compare keys for placement in the returned sequence.</param><param name="descending">true to sort the elements in descending order; false to sort the elements in ascending order.</param><typeparam name="TKey">The type of the key produced by <paramref name="keySelector"/>.</typeparam><filterpriority>2</filterpriority>
    public IOrderedEnumerable<T> CreateOrderedEnumerable<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer, bool descending)
    {
        throw new NotImplementedException();
    }

    /// <summary>
    /// Returns an enumerator that iterates through the collection.
    /// </summary>
    /// <returns>
    /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
    /// </returns>
    /// <filterpriority>1</filterpriority>
    public IEnumerator<T> GetEnumerator()
    {
        throw new NotImplementedException();
    }

    /// <summary>
    /// Returns an enumerator that iterates through a collection.
    /// </summary>
    /// <returns>
    /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
    /// </returns>
    /// <filterpriority>2</filterpriority>
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Чего я не понимаю, так это метода CreateOrderedEnumerable. Что именно он должен делать? Ну, я думаю, это, конечно, создаст упорядоченное перечисление, но как? Должен ли сам алгоритм сортировки идти туда? И что он будет сортировать? В этот метод не входит коллекция элементов, так где же он должен упорядочить коллекцию? Как бы вы использовали класс? Предназначен ли он для реализации, например, как частный вспомогательный класс внутри чего-то, что нужно сортировать?

Тогда вместо MyOrderedEnumerable<T> : IOrderedEnumerable<T> у вас может быть QuickSorter<T> : IOrderedEnumerable<T>, который берет коллекцию в своем конструкторе и сортирует ее при вызове этого метода CreateOrderedEnumerable... но что произойдет, если кто-то вызовет GetEnumerator и начнет перечислять до вызова этого метода?


Ха-ха, только что обнаружил, что некоторое время назад я задавал нечто подобное здесь. Но это было примерно так, если бы можно было вернуть один. Так что я думаю, что этот вопрос является ответом на единственный ответ, который я там получил =)


person Svish    schedule 05.08.2009    source источник


Ответы (2)


У меня есть пример реализации, который вы можете посмотреть. Он ни в коем случае не предназначен для того, чтобы быть эффективным, но он должен помочь вам начать работу.

По сути, IOrderedEnumerable<T> просто нужно иметь представление о своем текущем порядке, чтобы он мог создать новый. Предполагая, что у вас уже есть IComparer<T>, вы создаете новый, говоря что-то вроде:

int Compare(T first, T second)
{
    if (baseComparer != null)
    {
        int baseResult = baseComparer.Compare(first, second);
        if (baseResult != 0)
        {
            return baseResult;
        }
    }
    TKey firstKey = keySelector(first);
    TKey secondKey = keySelector(second);

    return comparer.Compare(firstKey, secondKey);        
}

Таким образом, вы создаете цепочку сравнений, идущую от «наименее значимого» до «наиболее значимого». Вам также нужно поместить туда бит «по убыванию», но это легко :)

В приведенном выше примере три разных аспекта представлены в трех разных классах, которые уже присутствуют в MiscUtil:

  • ReverseComparer: инвертирует существующие результаты IComparer<T>.
  • LinkedComparer: создает один компаратор из двух, с одним ведущим и одним подчиненным
  • ProjectionComparer: создает компаратор на основе проекции исходных элементов на ключи, делегируя другому компаратору возможность сравнить эти ключи.

Сравнители отлично подходят для объединения в цепочку, как это.

person Jon Skeet    schedule 05.08.2009
comment
Сладкий! Сразу проверю =) - person Svish; 05.08.2009
comment
Таким образом, он будет переупорядочивать себя на основе нового компаратора, который вы ему дадите? или? не уверен, что понял это... - person Svish; 05.08.2009
comment
Он не будет переупорядочивать себя — он создаст новую последовательность с новым порядком, основанную на старом порядке и новом сравнении. Он не будет использовать саму старую последовательность, кроме как для получения исходных неупорядоченных данных. Посмотрите на код для более подробной информации :) - person Jon Skeet; 05.08.2009
comment
Кстати, не расстраивайтесь из-за того, что меня это запутало - я тоже давно запутался. Требуется немного времени, чтобы прийти в себя. - person Jon Skeet; 05.08.2009
comment
Звучит отлично. Сейчас я смотрю на этот материал и многому учусь =) В вашем zip-файле также должна быть версия для практики, со всеми тестами, всеми использованиями и т. Д., Исправленными, чтобы все тесты не проходили. Затем можно было бы приступить к реализации и сделать так, чтобы тесты проходили один за другим: p - person Svish; 05.08.2009
comment
Также кажется, что отсутствует ThenBy... хотя я могу себе представить, как это было бы с LinkedComparer... кстати, это блестяще! Как вы, люди, придумываете все это?? :п - person Svish; 06.08.2009
comment
Я думаю, что теперь я получаю IOrderedEnumerable... сортировка идет в методе GetEnumerator, а затем этот CreateOrderedEnumerable создает новый IOrderedEnumerable с компаратором, который у него есть, и новым связанным. Это означает, что он будет использоваться для ThenBy. Я правильно понял? - person Svish; 06.08.2009
comment
Да, все звучит правильно :) ThenBy — это просто простая оболочка для CreateOrderedEnumerable. - person Jon Skeet; 06.08.2009
comment
Есть случай, когда вы можете схитрить. Если известно, что порядок является полным (каждый элемент определенно меньше, чем следующий, а не равен ему по критериям сортировки), то CreateOrderedEnumerable() может просто вернуть еще одну копию той же коллекции, потому что ThenBy не может повлиять на заказ. - person Jon Hanna; 25.01.2016
comment
@HereticMonkey: Спасибо за редактирование. Это обнаружило ошибку на сайте C# in Depth, которую я сейчас исправляю :) Откатит редактирование, когда оно заработает... - person Jon Skeet; 04.04.2019

Предположительно, ваш класс будет иметь некоторую внутреннюю переменную хранения, которая реализует IEnumerable (например, List<T>). Реализация этого метода в таком случае проста:

private List<T> data = new List<T>();

public IOrderedEnumerable<CalculationResult> CreateOrderedEnumerable<TKey>(Func<CalculationResult, TKey> keySelector, IComparer<TKey> comparer, bool descending)
{
  return descending ? 
      data.OrderByDescending(keySelector, comparer) 
    : data.OrderBy(keySelector, comparer);
}
person Jeremy Wilson    schedule 04.03.2012
comment
Это не правильно. CreateOrderedEnumerable вызывается функцией ThenBy LINQ и должна сохранять уже существующий порядок. Использование вашего фрагмента для реализации CreateOrderedEnumerable переопределит порядок, в результате чего ваша реализация нарушит семантику интерфейса. - person Zarat; 29.05.2015