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


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

[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"));

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

22 87

22 ответа:

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

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

3:xey 6: fty 7: aaa

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

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;
    }
}

EDIT

чтобы вернуть 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
добавлено Katy aged 51 at index 8
удалена Мэри в возрасте 41 года при индексе 3
добавлена Мэри в возрасте 41 при индексе 7
удалена Джейн в возрасте 20 лет при индексе 3
добавлена Джейн в возрасте 20 лет при индексе 5
удален Джим в возрасте 39 лет при индексе 3
добавлен Джим в возрасте 39 лет при индексе 6
удалена Джейн в возрасте 20 лет индекс 4
добавлена Джейн в возрасте 20 лет при индексе 5

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

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

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();

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

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

обновление

The ObservableSortedList что @romkyns в комментариях автоматически поддерживает порядок сортировки.

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

однако обратите внимание и на замечание

может быть багги из-за сравнительной сложности интерфейса и его относительно плохой документации (см. https://stackoverflow.com/a/5883947/33080).

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

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/

мне понравился подход метода расширения сортировки пузырьков в блоге "Ричи"выше, но я не обязательно хочу просто сортировать сравнение всего объекта. Я чаще всего хочу Сортировать по определенному свойству объекта. Поэтому я изменил его, чтобы принять селектор клавиш так, как это делает 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);

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 для документации и другие функции.

Я хочу добавить к ответу Нила. Чтобы включить метод, который напоминает orderby. Добавьте этот метод в качестве дополнения:

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);

ответ @ 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);

вариант, где вы сортируете коллекцию на месте с помощью сортировка выбор. Элементы перемещаются на место с помощью 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();

чтобы немного улучшить метод расширения в ответе 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);
            }
        }
    }

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

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

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

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

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

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

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)))));

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

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

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

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

Я думаю, что это наиболее элегантное решение:

http://www.xamlplayground.org/post/2009/07/18/Use-CollectionViewSource-effectively-in-MVVM-applications.aspx

что за черт, я брошу в быстро-булыжник-вместе ответ как well...it выглядит немного как некоторые другие реализации здесь, но я добавлю его 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
    }
}

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

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 - моя публика класс. Просто казалось намного проще. Добавленное преимущество: он действительно работает и не связывается с привязками или возвращает новую коллекцию и т. д.

хорошо, так как у меня были проблемы с получением 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.

Это то, что я делаю с расширениями 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);
        }
    }

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

// 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);

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

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));
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));