Как сортировать наблюдаемую коллекцию?

У меня есть следующий класс:

[DataContract]
public class Pair<TKey, TValue> : INotifyPropertyChanged, IDisposable
{
    public Pair(TKey key, TValue value)
    {
        Key = key;
        Value = value;
    }

    #region Properties
    [DataMember]
    public TKey Key
    {
        get
        { return m_key; }
        set
        {
            m_key = value;
            OnPropertyChanged("Key");
        }
    }
    [DataMember]
    public TValue Value
    {
        get { return m_value; }
        set
        {
            m_value = value;
            OnPropertyChanged("Value");
        }
    }
    #endregion

    #region Fields
    private TKey m_key;
    private TValue m_value;
    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }

    #endregion

    #region IDisposable Members

    public void Dispose()
    { }

    #endregion
}

Что я ввел в ObservableCollection:

ObservableCollection<Pair<ushort, string>> my_collection = 
    new ObservableCollection<Pair<ushort, string>>();

my_collection.Add(new Pair(7, "aaa"));
my_collection.Add(new Pair(3, "xey"));
my_collection.Add(new Pair(6, "fty"));

В: Как отсортировать его по ключу?

Ответ 1

OP Edit: многие из них правильно указали, что исходный ответ не возвращает одну и ту же коллекцию (первоначально сфокусированный на сортировке словарной части Q). См. Редактирование внизу, где я обращаюсь к сортировке наблюдаемой коллекции. Оригинал остался здесь, все еще получая голоса

Вы можете использовать linq, как показывает метод doSort ниже. Быстрый фрагмент кода: создает

3: Xey 6: FTY 7: ааа

В качестве альтернативы вы можете использовать метод расширения в самой коллекции

var sortedOC = _collection.OrderBy(i => i.Key);

private void doSort()
{
    ObservableCollection<Pair<ushort, string>> _collection = 
        new ObservableCollection<Pair<ushort, string>>();

    _collection.Add(new Pair<ushort,string>(7,"aaa"));
    _collection.Add(new Pair<ushort, string>(3, "xey"));
    _collection.Add(new Pair<ushort, string>(6, "fty"));

    var sortedOC = from item in _collection
                   orderby item.Key
                   select item;

    foreach (var i in sortedOC)
    {
        Debug.WriteLine(i);
    }

}

public class Pair<TKey, TValue>
{
    private TKey _key;

    public TKey Key
    {
        get { return _key; }
        set { _key = value; }
    }
    private TValue _value;

    public TValue Value
    {
        get { return _value; }
        set { _value = value; }
    }

    public Pair(TKey key, TValue value)
    {
        _key = key;
        _value = value;

    }

    public override string ToString()
    {
        return this.Key + ":" + this.Value;
    }
}

ИЗМЕНИТЬ

Чтобы вернуть ObservableCollection, вызовите .ToObservableCollection на sortedOC, используя, например, эта реализация.

OP EDIT Сортировка наблюдаемого и возвращающего один и тот же отсортированный объект может быть выполнена с использованием метода расширения. Для больших коллекций следите за количеством измененных уведомлений об коллекции, например

public static void Sort<T>(this ObservableCollection<T> observable) where T : IComparable<T>, IEquatable<T>
    {
        List<T> sorted = observable.OrderBy(x => x).ToList();

        int ptr = 0;
        while (ptr < sorted.Count)
        {
            if (!observable[ptr].Equals(sorted[ptr]))
            {
                T t = observable[ptr];
                observable.RemoveAt(ptr);
                observable.Insert(sorted.IndexOf(t), t);
            }
            else
            {
                ptr++;
            }
        }
    }

использование: Образец с наблюдателем (используется класс Person, чтобы он был простым)

public class Person:IComparable<Person>,IEquatable<Person>
    { 
        public string Name { get; set; }
        public int Age { get; set; }

        public int CompareTo(Person other)
        {
            if (this.Age == other.Age) return 0;
            return this.Age.CompareTo(other.Age);
        }

        public override string ToString()
        {
            return Name + " aged " + Age;
        }

        public bool Equals(Person other)
        {
            if (this.Name.Equals(other.Name) && this.Age.Equals(other.Age)) return true;
            return false;
        }
    }

  static void Main(string[] args)
    {
        Console.WriteLine("adding items...");
        var observable = new ObservableCollection<Person>()
        {
            new Person { Name = "Katy", Age = 51 },
            new Person { Name = "Jack", Age = 12 },
            new Person { Name = "Bob",  Age = 13 },
            new Person { Name = "John", Age = 14 },
            new Person { Name = "Mary", Age = 41 },
            new Person { Name = "Jane", Age = 20 },
            new Person { Name = "Jim",  Age = 39 },
            new Person { Name = "Sue",  Age = 15 },
            new Person { Name = "Kim",  Age = 19 }
        };

        //what do observers see?
        observable.CollectionChanged += (o, e) => {

            if (e.OldItems != null)
            {
                foreach (var item in e.OldItems)
                {
                    Console.WriteLine("removed {0} at index {1}", item, e.OldStartingIndex);
                }
            }

            if (e.NewItems != null)
            {
                foreach (var item in e.NewItems)
                {
                    Console.WriteLine("added {0} at index {1}", item, e.NewStartingIndex);
                }
            }};            

        Console.WriteLine("\nsorting items...");
        observable.Sort();
    };

Вывод сверху:
удалил Кэти в возрасте 51 лет по индексу 0
добавила Кэти в возрасте 51 лет по индексу 8
удалила Мэри в возрасте 41 года по индексу 3
добавила Мэри в возрасте 41 года по индексу 7
удалила Джейн в возрасте 20 лет по индексу 3
добавила Джейн в возрасте 20 лет по индексу 5
удалил Джима в возрасте 39 лет по индексу 3
добавил Джим в возрасте 39 лет по индексу 6
удалила Джейн в возрасте 20 лет по индексу 4
добавила Джейн в возрасте 20 лет по индексу 5

Класс Person реализует как IComparable, так и IEquatable, последний используется для минимизации изменений в коллекции, чтобы уменьшить количество уведомлений об изменениях, поднятых

Ответ 2

Это простое расширение прекрасно работало для меня. Я просто должен был убедиться, что MyObject был IComparable. Когда метод sort вызывается в наблюдаемом наборе MyObjects, вызывается метод CompareTo на MyObject, который вызывает мой метод Logical Sort. Хотя у него нет всех колоколов и свистков остальных ответов, размещенных здесь, это именно то, что мне нужно.

static class Extensions
{
    public static void Sort<T>(this ObservableCollection<T> collection) where T : IComparable
    {
        List<T> sorted = collection.OrderBy(x => x).ToList();
        for (int i = 0; i < sorted.Count(); i++)
            collection.Move(collection.IndexOf(sorted[i]), i);
    }
}

public class MyObject: IComparable
{
    public int CompareTo(object o)
    {
        MyObject a = this;
        MyObject b = (MyObject)o;
        return Utils.LogicalStringCompare(a.Title, b.Title);
    }

    public string Title;

}
  .
  .
  .
myCollection = new ObservableCollection<MyObject>();
//add stuff to collection
myCollection.Sort();

Ответ 3

Я знаю, что этот вопрос старый, но только что произошел во время поиска в Интернете и нашел соответствующую запись в блоге, которая дает лучший ответ, чем те, которые есть здесь:

http://kiwigis.blogspot.com/2010/03/how-to-sort-obversablecollection.html

UPDATE

ObservableSortedList, который указывает @romkyns в комментариях, автоматически поддерживает порядок сортировки.

Реализует наблюдаемую коллекцию, которая сохраняет свои элементы в отсортированном порядке. В частности, обрабатываются изменения свойств элемента, которые приводят к изменениям порядка.

Однако заметьте также замечание

Может быть ошибкой из-за сравнительной сложности интерфейса и его относительно плохой документации (см. fooobar.com/questions/68041/...).

Ответ 4

Вы можете использовать этот простой метод:

public static void Sort<TSource, TKey>(this Collection<TSource> source, Func<TSource, TKey> keySelector)
{
    List<TSource> sortedList = source.OrderBy(keySelector).ToList();
    source.Clear();
    foreach (var sortedItem in sortedList)
        source.Add(sortedItem);
}

Вы можете сделать следующее:

_collection.Sort(i => i.Key);

Подробнее: http://jaider.net/2011-05-04/sort-a-observablecollection/

Ответ 5

Мне понравился подход метода расширения пузырьков в блоге "Ричи" выше, но мне не обязательно просто сортировать сравнение всего объекта. Я чаще всего хочу сортировать по определенному свойству объекта. Поэтому я изменил его, чтобы принять ключевой селектор, как делает OrderBy, чтобы вы могли выбрать, какое свойство сортировать:

    public static void Sort<TSource, TKey>(this ObservableCollection<TSource> source, Func<TSource, TKey> keySelector)
    {
        if (source == null) return;

        Comparer<TKey> comparer = Comparer<TKey>.Default;

        for (int i = source.Count - 1; i >= 0; i--)
        {
            for (int j = 1; j <= i; j++)
            {
                TSource o1 = source[j - 1];
                TSource o2 = source[j];
                if (comparer.Compare(keySelector(o1), keySelector(o2)) > 0)
                {
                    source.Remove(o1);
                    source.Insert(j, o1);
                }
            }
        }
    }

Что бы вы назвали так же, как вы бы назвали OrderBy, кроме того, что он сортирует существующий экземпляр вашего ObservableCollection вместо возврата новой коллекции:

ObservableCollection<Person> people = new ObservableCollection<Person>();
...

people.Sort(p => p.FirstName);

Ответ 6

WPF предоставляет живую сортировку из коробки с помощью класса ListCollectionView...

public ObservableCollection<string> MyStrings { get; set; }
private ListCollectionView _listCollectionView;
private void InitializeCollection()
{
    MyStrings = new ObservableCollection<string>();
    _listCollectionView = CollectionViewSource.GetDefaultView(MyStrings) 
              as ListCollectionView;
    if (_listCollectionView != null)
    {
        _listCollectionView.IsLiveSorting = true;
        _listCollectionView.CustomSort = new 
                CaseInsensitiveComparer(CultureInfo.InvariantCulture);
    }
}

Как только эта инициализация завершена, вам больше нечего делать. Преимущество над пассивным типом заключается в том, что ListCollectionView делает весь тяжелый подъем таким образом, который прозрачен для разработчика. Новые элементы автоматически помещаются в их правильный порядок сортировки. Любой класс, полученный из IComparer of T, подходит для пользовательского свойства сортировки.

См. ListCollectionView для документации и других функций.

Ответ 7

Вариант - это то, где вы сортируете коллекцию на месте с помощью алгоритма сортировка сортировки. Элементы перемещаются на место с помощью метода Move. Каждое движение запускает событие CollectionChanged с NotifyCollectionChangedAction.Move (а также PropertyChanged с именем свойства Item[]).

Этот алгоритм обладает некоторыми хорошими свойствами:

  • Алгоритм может быть реализован как устойчивый вид.
  • Количество элементов, перемещаемых в коллекции (например, CollectionChanged), почти всегда меньше, чем другие аналогичные алгоритмы, такие как сортировка вставки и сортировка пузырьков.

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

Вот метод расширения, который для простоты требует, чтобы элементы реализовали IComparable<T>. Другие опции используют IComparer<T> или Func<T, T, Int32>.

public static class ObservableCollectionExtensions {

  public static void Sort<T>(this ObservableCollection<T> collection) where T : IComparable<T> {
    if (collection == null)
      throw new ArgumentNullException("collection");

    for (var startIndex = 0; startIndex < collection.Count - 1; startIndex += 1) {
      var indexOfSmallestItem = startIndex;
      for (var i = startIndex + 1; i < collection.Count; i += 1)
        if (collection[i].CompareTo(collection[indexOfSmallestItem]) < 0)
          indexOfSmallestItem = i;
      if (indexOfSmallestItem != startIndex)
        collection.Move(indexOfSmallestItem, startIndex);
    }
  }

}

Сортировка коллекции - это просто вызов метода расширения:

var collection = new ObservableCollection<String>(...);
collection.Sort();

Ответ 8

Я хотел бы Добавить в ответ NeilW. Включить метод, похожий на порядок. Добавьте этот метод как расширение:

public static void Sort<T>(this ObservableCollection<T> collection, Func<T,T> keySelector) where T : IComparable
{
    List<T> sorted = collection.OrderBy(keySelector).ToList();
    for (int i = 0; i < sorted.Count(); i++)
        collection.Move(collection.IndexOf(sorted[i]), i);
}

И используйте как:

myCollection = new ObservableCollection<MyObject>();

//Sorts in place, on a specific Func<T,T>
myCollection.Sort(x => x.ID);

Ответ 9

@NielW ответ - это путь для реальной сортировки на месте. Я хотел добавить немного измененное решение, которое позволяет обойти использование IComparable:

static class Extensions
{
    public static void Sort<TSource, TKey>(this ObservableCollection<TSource> collection, Func<TSource, TKey> keySelector)
    {
        List<TSource> sorted = collection.OrderBy(keySelector).ToList();
        for (int i = 0; i < sorted.Count(); i++)
            collection.Move(collection.IndexOf(sorted[i]), i);
    }
}

теперь вы можете вызвать его, как и любой метод LINQ:

myObservableCollection.Sort(o => o.MyProperty);

Ответ 10

Чтобы немного улучшить метод расширения на xr280xr, я добавил необязательный параметр bool, чтобы определить, уменьшается ли сортировка или нет. Я также включил предложение, сделанное Карлосом П. в комментарии к этому ответу. См. Ниже.

public static void Sort<TSource, TKey>(this ObservableCollection<TSource> source, Func<TSource, TKey> keySelector, bool desc = false)
    {
        if (source == null) return;

        Comparer<TKey> comparer = Comparer<TKey>.Default;

        for (int i = source.Count - 1; i >= 0; i--)
        {
            for (int j = 1; j <= i; j++)
            {
                TSource o1 = source[j - 1];
                TSource o2 = source[j];
                int comparison = comparer.Compare(keySelector(o1), keySelector(o2));
                if (desc && comparison < 0)
                    source.Move(j, j - 1);
                else if (!desc && comparison > 0)
                    source.Move(j - 1, j);
            }
        }
    }

Ответ 11

Вам нужно постоянно сортировать свою коллекцию? При извлечении пар вам нужно, чтобы они всегда сортировались, или это только несколько раз (возможно, только для представления)? Насколько вы ожидаете от своей коллекции? Есть много факторов, которые могут помочь вам выбрать способ использования ведьмы.

Если вам нужна коллекция, которую нужно сортировать в любое время, даже если вы вставляете или удаляете элементы и скорость вставки не является проблемой, возможно, вам следует реализовать какой-то SortedObservableCollection, например, @Gerrie Schenck, упомянутый или зарегистрированный эта реализация.

Если вам нужна ваша коллекция, отсортированная всего в несколько раз, используйте:

my_collection.OrderBy(p => p.Key);

Это займет некоторое время, чтобы отсортировать коллекцию, но даже в этом случае это может быть лучшим решением в зависимости от того, что вы делаете с ним.

Ответ 12

Мой текущий ответ уже имеет больше всего голосов, но я нашел лучший и более современный способ сделать это.

class MyObject 
{
      public int id { get; set; }
      public string title { get; set; }
}

ObservableCollection<MyObject> myCollection = new ObservableCollection<MyObject>();

//add stuff to collection
// .
// .
// .

myCollection = new ObservableCollection<MyObject>(
    myCollection.OrderBy(n => n.title, Comparer<string>.Create(
    (x, y) => (Utils.Utils.LogicalStringCompare(x, y)))));

Ответ 13

Создайте новый класс SortedObservableCollection, выведите его из ObservableCollection и реализуйте IComparable<Pair<ushort, string>>.

Ответ 14

Одним из способов было бы преобразовать его в список, а затем вызвать Sort(), предоставляя делегат сравнения. Что-то вроде: -

(непроверенные)

my_collection.ToList().Sort((left, right) => left == right ? 0 : (left > right ? -1 : 1));

Ответ 16

Что, черт возьми, я тоже вложу ответ быстро-сморщивающийся... он немного похож на некоторые другие реализации здесь, но я добавлю его anywho:

(едва тестировался, надеюсь, я не смущаюсь)

Сначала укажите некоторые цели (мои предположения):

1) Должен сортировать ObservableCollection<T> на месте, поддерживать уведомления и т.д.

2) Не должно быть ужасно неэффективным (т.е. что-то близкое к стандартной "хорошей" эффективности сортировки)

public static class Ext
{
    public static void Sort<T>(this ObservableCollection<T> src)
        where T : IComparable<T>
    {
        // Some preliminary safety checks
        if(src == null) throw new ArgumentNullException("src");
        if(!src.Any()) return;

        // N for the select,
        // + ~ N log N, assuming "smart" sort implementation on the OrderBy
        // Total: N log N + N (est)
        var indexedPairs = src
            .Select((item,i) => Tuple.Create(i, item))
            .OrderBy(tup => tup.Item2);
        // N for another select
        var postIndexedPairs = indexedPairs
            .Select((item,i) => Tuple.Create(i, item.Item1, item.Item2));
        // N for a loop over every element
        var pairEnum = postIndexedPairs.GetEnumerator();
        pairEnum.MoveNext();
        for(int idx = 0; idx < src.Count; idx++, pairEnum.MoveNext())
        {
            src.RemoveAt(pairEnum.Current.Item1);
            src.Insert(idx, pairEnum.Current.Item3);            
        }
        // (very roughly) Estimated Complexity: 
        // N log N + N + N + N
        // == N log N + 3N
    }
}

Ответ 17

Ни один из этих ответов не работал в моем случае. Либо потому, что он затягивает привязку, либо требует так много дополнительного кодирования, что это своего рода кошмар, или ответ просто сломан. Итак, вот еще один более простой ответ, который я подумал. Это намного меньше кода, и он остается той же наблюдаемой коллекцией с дополнительным этим типом метода. Дайте мне знать, если есть какая-то причина, по которой я не должен делать это таким образом (эффективность и т.д.)?

public class ScoutItems : ObservableCollection<ScoutItem>
{
    public void Sort(SortDirection _sDir, string _sItem)
    {
             //TODO: Add logic to look at _sItem and decide what property to sort on
            IEnumerable<ScoutItem> si_enum = this.AsEnumerable();

            if (_sDir == SortDirection.Ascending)
            {
                si_enum = si_enum.OrderBy(p => p.UPC).AsEnumerable();
            } else
            {
                si_enum = si_enum.OrderByDescending(p => p.UPC).AsEnumerable();
            }

            foreach (ScoutItem si in si_enum)
            {
                int _OldIndex = this.IndexOf(si);
                int _NewIndex = si_enum.ToList().IndexOf(si);
                this.MoveItem(_OldIndex, _NewIndex);
            }
      }
}

... Где ScoutItem - мой открытый класс. Просто казалось намного проще. Добавленная выгода: она действительно работает и не путается с привязками или возвращает новую коллекцию и т.д.

Ответ 18

Хорошо, так как у меня возникли проблемы с ObservableSortedList для работы с XAML, я пошел и создал SortingObservableCollection. Он наследуется от ObservableCollection, поэтому он работает с XAML, и я тестировал его на 98% охвата кода. Я использовал его в своих приложениях, но я не буду обещать, что это ошибка. Не стесняйтесь вносить свой вклад. Вот пример использования кода:

var collection = new SortingObservableCollection<MyViewModel, int>(Comparer<int>.Default, model => model.IntPropertyToSortOn);

collection.Add(new MyViewModel(3));
collection.Add(new MyViewModel(1));
collection.Add(new MyViewModel(2));
// At this point, the order is 1, 2, 3
collection[0].IntPropertyToSortOn = 4; // As long as IntPropertyToSortOn uses INotifyPropertyChanged, this will cause the collection to resort correctly

Это PCL, поэтому он должен работать с Windows Store, Windows Phone и .NET 4.5.1.

Ответ 19

Это сработало для меня, где-то давно это было.

// SortableObservableCollection
public class SortableObservableCollection<T> : ObservableCollection<T>
    {
        public SortableObservableCollection(List<T> list)
            : base(list)
        {
        }

        public SortableObservableCollection()
        {
        }

        public void Sort<TKey>(Func<T, TKey> keySelector, System.ComponentModel.ListSortDirection direction)
        {
            switch (direction)
            {
                case System.ComponentModel.ListSortDirection.Ascending:
                    {
                        ApplySort(Items.OrderBy(keySelector));
                        break;
                    }
                case System.ComponentModel.ListSortDirection.Descending:
                    {
                        ApplySort(Items.OrderByDescending(keySelector));
                        break;
                    }
            }
        }

        public void Sort<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer)
        {
            ApplySort(Items.OrderBy(keySelector, comparer));
        }

        private void ApplySort(IEnumerable<T> sortedItems)
        {
            var sortedItemsList = sortedItems.ToList();

            foreach (var item in sortedItemsList)
            {
                Move(IndexOf(item), sortedItemsList.IndexOf(item));
            }
        }
    }

Использование:

MySortableCollection.Sort(x => x, System.ComponentModel.ListSortDirection.Ascending);

Ответ 20

Это то, что я делаю с расширениями OC:

    /// <summary>
    /// Synches the collection items to the target collection items.
    /// This does not observe sort order.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source">The items.</param>
    /// <param name="updatedCollection">The updated collection.</param>
    public static void SynchCollection<T>(this IList<T> source, IEnumerable<T> updatedCollection)
    {
        // Evaluate
        if (updatedCollection == null) return;

        // Make a list
        var collectionArray = updatedCollection.ToArray();

        // Remove items from FilteredViewItems not in list
        source.RemoveRange(source.Except(collectionArray));

        // Add items not in FilteredViewItems that are in list
        source.AddRange(collectionArray.Except(source));
    }

    /// <summary>
    /// Synches the collection items to the target collection items.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source">The source.</param>
    /// <param name="updatedCollection">The updated collection.</param>
    /// <param name="canSort">if set to <c>true</c> [can sort].</param>
    public static void SynchCollection<T>(this ObservableCollection<T> source,
        IList<T> updatedCollection, bool canSort = false)
    {
        // Synch collection
        SynchCollection(source, updatedCollection.AsEnumerable());

        // Sort collection
        if (!canSort) return;

        // Update indexes as needed
        for (var i = 0; i < updatedCollection.Count; i++)
        {
            // Index of new location
            var index = source.IndexOf(updatedCollection[i]);
            if (index == i) continue;

            // Move item to new index if it has changed.
            source.Move(index, i);
        }
    }

Ответ 21

Мне нужно было сортировать по нескольким вещам, а не по одному. Этот ответ основан на некоторых других ответах, но он позволяет более сложную сортировку.

static class Extensions
{
    public static void Sort<T, TKey>(this ObservableCollection<T> collection, Func<ObservableCollection<T>, TKey> sort)
    {
        var sorted = (sort.Invoke(collection) as IOrderedEnumerable<T>).ToArray();
        for (int i = 0; i < sorted.Count(); i++)
            collection.Move(collection.IndexOf(sorted[i]), i);
    }
}

Когда вы его используете, переходите к серии вызовов OrderBy/ThenBy. Вот так:

Children.Sort(col => col.OrderByDescending(xx => xx.ItemType == "drive")
                    .ThenByDescending(xx => xx.ItemType == "folder")
                    .ThenBy(xx => xx.Path));

Ответ 22

var collection = new ObservableCollection<int>();

collection.Add(7);
collection.Add(4);
collection.Add(12);
collection.Add(1);
collection.Add(20);

// ascending
collection = new ObservableCollection<int>(collection.OrderBy(a => a));

// descending
collection = new ObservableCollection<int>(collection.OrderByDescending(a => a));