87

I have below ObservableCollection<string>. I need to sort this alphabetically.

private ObservableCollection<string> _animals = new ObservableCollection<string>
{
    "Cat", "Dog", "Bear", "Lion", "Mouse",
    "Horse", "Rat", "Elephant", "Kangaroo", "Lizard", 
    "Snake", "Frog", "Fish", "Butterfly", "Human", 
    "Cow", "Bumble Bee"
};

I tried _animals.OrderByDescending. But I don't know how to use it correctly.

_animals.OrderByDescending(a => a.<what_is_here_?>);

How can I do this?

double-beep
  • 5,031
  • 17
  • 33
  • 41
Bishan
  • 15,211
  • 52
  • 164
  • 258
  • Possible duplicate: http://stackoverflow.com/questions/16562175/, http://stackoverflow.com/questions/5803786/. – Sergey Vyacheslavovich Brunov Oct 01 '13 at 09:51
  • Take a look here http://stackoverflow.com/questions/3973137/order-a-observablecollectiont-without-creating-a-new-one/27382401#27382401 – NoWar Dec 09 '14 at 15:17
  • Most answers that deal with `Move` answers dont work correctly when there are duplicates in the collection. See this for a correct implementation: https://stackoverflow.com/a/1945701/ – nawfal Aug 23 '20 at 19:08

15 Answers15

151

Introduction

Basically, if there is a need to display a sorted collection, please consider using the CollectionViewSource class: assign ("bind") its Source property to the source collection — an instance of the ObservableCollection<T> class.

The idea is that CollectionViewSource class provides an instance of the CollectionView class. This is kind of "projection" of the original (source) collection, but with applied sorting, filtering, etc.

References:

Live Shaping

WPF 4.5 introduces "Live Shaping" feature for CollectionViewSource.

References:

Solution

If there still a need to sort an instance of the ObservableCollection<T> class, here is how it can be done. The ObservableCollection<T> class itself does not have sort method. But, the collection could be re-created to have items sorted:

// Animals property setter must raise "property changed" event to notify binding clients.
// See INotifyPropertyChanged interface for details.
Animals = new ObservableCollection<string>
    {
        "Cat", "Dog", "Bear", "Lion", "Mouse",
        "Horse", "Rat", "Elephant", "Kangaroo",
        "Lizard", "Snake", "Frog", "Fish",
        "Butterfly", "Human", "Cow", "Bumble Bee"
    };
...
Animals = new ObservableCollection<string>(Animals.OrderBy(i => i));

Additional details

Please note that OrderBy() and OrderByDescending() methods (as other LINQ–extension methods) do not modify the source collection! They instead create a new sequence (i.e. a new instance of the class that implements IEnumerable<T> interface). Thus, it is necessary to re-create the collection.

  • 1
    CollectionViewSource should be used wherever is possible but Xamarin Forms does not have this so it is not possible. The second solution of using OrderBy is not feasible, because the assumption is that the UI is listening to the observable collection because it implement INotifyCollectionChanged. If you create a new instance, what's the point? The UI will do a full refresh. – Christian Findlay Feb 11 '18 at 07:12
  • There is no future for the CollectionViewSource in UWP. This is a dead-end pattern. – Quark Soup Jun 09 '19 at 20:30
  • @Quarkly What's the recommended solution for UWP? – dgellow Apr 22 '21 at 08:15
  • I've been using LINQ to sort the observable collections. For animation, I've actually gone the extra step to perform a diff on the old collection order vs the new collection order and then perform the minimal RemoveAt/InsertAt operations to place them in the right order. It's a lot of extra work, but it gives the animation a much more natural look. – Quark Soup Apr 22 '21 at 14:07
69

The way

The way I would go is to build a List<> starting from the ObservableCollection<>, sort it (through its Sort() method, more on msdn) and when the List<> has been sorted, reorder the ObservableCollection<> with the Move() method.

The code

public static void Sort<T>(this ObservableCollection<T> collection, Comparison<T> comparison)
{
    var sortableList = new List<T>(collection);
    sortableList.Sort(comparison);

    for (int i = 0; i < sortableList.Count; i++)
    {
        collection.Move(collection.IndexOf(sortableList[i]), i);
    }
}

The test

public void TestObservableCollectionSortExtension()
{
    var observableCollection = new ObservableCollection<int>();
    var maxValue = 10;

    // Populate the list in reverse mode [maxValue, maxValue-1, ..., 1, 0]
    for (int i = maxValue; i >= 0; i--)
    {
        observableCollection.Add(i);
    }

    // Assert the collection is in reverse mode
    for (int i = maxValue; i >= 0; i--)
    {
        Assert.AreEqual(i, observableCollection[maxValue - i]);
    }

    // Sort the observable collection
    observableCollection.Sort((a, b) => { return a.CompareTo(b); });

    // Assert elements have been sorted
    for (int i = 0; i < maxValue; i++)
    {
        Assert.AreEqual(i, observableCollection[i]);
    }
}

##Notes This is just a proof of concept, showing how to sort an ObservableCollection<> without breaking the bindings on items.The sort algorithm has room for improvements and validations (like index checking as pointed out here).

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
Marco
  • 744
  • 6
  • 14
  • Works great. I was just looking for a solution that won't break the binding, thanks Marco. – some_name Apr 30 '16 at 14:56
  • 3
    Note: According to MSDN, the "ObservableCollection.IndexOf" Method is "an O(n) operation, where n is Count" and I suspect the "ObservableCollection.Move" Method calls the "ObservableCollection.RemoveAt" and "ObservableCollection.Insert" Methods each of which are (again, according to MSDN) also "an O(n) operation, where n is Count". Continued in next Comment... – Tom Jul 06 '16 at 23:01
  • 2
    *If* run-time is significant, I suspect you're much better off re-creating the "ObservableCollection" using the sorted List (i.e. by using the "ObservableCollection(List)" Constructor). As I just commented on "D_Learning"'s Comment to "Sergey Brunov"'s Answer above: "If you (as you would when using, say, the MVVM Pattern) used a Property (inside a Class that Implements "INotifyPropertyChanged" Interface) to Set the "ObservableCollection" to the new sorted version and the Setter calls the Interface's "PropertyChanged" Event, then the Binding would not be broken." – Tom Jul 06 '16 at 23:01
  • 11
    Bindings wouldn't be broken, but all the subscriptions (in code) to any item's event would. On top of that, recreating the collection from scratch, the binded UI element (would it be a listview or whatever you want) might have to recreate all the visual items potentially taking a lot more (in time and/or memory consumption) then a 3*O(n) iteration... So, finally the right answer is always the same: it depends. This is just an alternative way to go when you can't or do not want to rebuild your list. – Marco Jul 13 '16 at 13:37
  • I was able to implement this and it worked great in my WPF app where, if the bindings broke, the collection would not have populated in my ListBoxes. The data came through sorted back into the boxes just fine. So say I have a model: `public class Computer { public string Name { get; set; } }`, to where I wanted alphabetized `Name` values, and I had an `ObservableCollection myList`, then it was: `myList.Sort((a, b) => { return a.Name.CompareTo(b.Name); });`. Remember when adding the method Marco provided that it goes in a `public static class Extensions { ... }` in your namespace. – vapcguy Jan 19 '17 at 17:40
  • You should add a check for equality of indices in your sort algorithm or you might run into problems: http://stackoverflow.com/questions/42204898/why-is-the-combobox-losing-its-selecteditem-when-sorting-the-itemssource/42204899#42204899 – Tim Pohlmann Feb 13 '17 at 15:21
  • I proposed an edit to this answer which would fix the issue but apparently people thought this would change content of the answer to much. – Tim Pohlmann Feb 13 '17 at 15:22
  • The equality check was necessary for me to use this without it crashing. If sortableList[i] == i then it would throw an exception. Adding "if (collection.IndexOf(sortableList[i]) != i)" before the collection.Move fixed that issue and the collection sorted correctly. – b.pell May 12 '19 at 19:40
  • Too damned slow for any reasonably sized data set, even with the optimizations. – Quark Soup Jun 13 '19 at 00:22
  • 1
    If you need to handle reordering over big data set, ObservableCollection may not be the best choice (when you need to keep the bindings alive). A CollectionViewSource is probably better suited for that... – Marco Jun 13 '19 at 14:40
  • 7
    I can't stress this enough: **There is no future in the CollectionViewSource**. It isn't supported in UWP and you will be stuck with an obsolete design if you depend on it now. Having been burned on this exact issue, stick with the **ObservableCollection** if you want to port your code to Windows 10 someday in the future. – Quark Soup Jun 13 '19 at 22:44
  • CollectionViewSources also don't allow you to specify exactly when to sort the list, from what i've seen. In my case, we don't want to sort the list until the user isn't looking at it anymore – gusmally supports Monica Aug 08 '19 at 22:13
20

I looked at these, I was getting it sorted, and then it broke the binding, as above. Came up with this solution, though simpler than most of yours, it appears to do what I want to,,,

public static ObservableCollection<string> OrderThoseGroups( ObservableCollection<string> orderThoseGroups)
    {
        ObservableCollection<string> temp;
        temp =  new ObservableCollection<string>(orderThoseGroups.OrderBy(p => p));
        orderThoseGroups.Clear();
        foreach (string j in temp) orderThoseGroups.Add(j);
        return orderThoseGroups;



    }
John Leone
  • 273
  • 2
  • 9
  • 1
    This is far superior to the other solutions. #1 it doesn't break the bindings, #2, it doesn't rely on the **CollectionViewSource** which is an obsolete pattern, #3 it's simple, #4 it's blindingly fast compared to the 'Move Items Around' solution. – Quark Soup Jun 13 '19 at 00:21
  • 4
    While this seems to be way faster than moving all items around, it also triggers different events (remove and add instead of move). This might not be an issue depending on the use-case but is definitely a thing to keep in mind. – Tim Pohlmann Jun 13 '19 at 15:17
  • @TimPohlmann - The Move triggers events as well, so the difference is O operations with the 'Move' algorithm vs O + 1 operations with the clear and add algorithm. – Quark Soup Jun 13 '19 at 22:42
  • 2
    @DonaldAirey I was talking about the fact that the events are being triggered with different event arg contents. Depending on what you event handlers are listening for, this could cause some issues. – Tim Pohlmann Jun 14 '19 at 06:43
17

This extension method eliminates the need to sort the entire list.

Instead, it inserts each new item in place. So the list is always remains sorted.

It turns out that this method just works when a lot of the other methods fail due to missing notifications when the collection changes. And it is rather fast.

The code below should be bulletproof; it has been extensively tested in a large-scale production environment.

To use:

// Call on dispatcher.
ObservableCollection<MyClass> collectionView = new ObservableCollection<MyClass>();
var p1 = new MyClass() { Key = "A" }
var p2 = new MyClass() { Key = "Z" }
var p3 = new MyClass() { Key = "D" }
collectionView.InsertInPlace(p1, o => o.Key);
collectionView.InsertInPlace(p2, o => o.Key);
collectionView.InsertInPlace(p3, o => o.Key);
// The list will always remain ordered on the screen, e.g. "A, D, Z" .
// Insertion speed is Log(N) as it uses a binary search.

And the extension method:

/// <summary>
/// Inserts an item into a list in the correct place, based on the provided key and key comparer. Use like OrderBy(o => o.PropertyWithKey).
/// </summary>
public static void InsertInPlace<TItem, TKey>(this ObservableCollection<TItem> collection, TItem itemToAdd, Func<TItem, TKey> keyGetter)
{
    int index = collection.ToList().BinarySearch(keyGetter(itemToAdd), Comparer<TKey>.Default, keyGetter);
    collection.Insert(index, itemToAdd);
}

And the binary search extension method:

/// <summary>
/// Binary search.
/// </summary>
/// <returns>Index of item in collection.</returns> 
/// <notes>This version tops out at approximately 25% faster than the equivalent recursive version. This 25% speedup is for list
/// lengths more of than 1000 items, with less performance advantage for smaller lists.</notes>
public static int BinarySearch<TItem, TKey>(this IList<TItem> collection, TKey keyToFind, IComparer<TKey> comparer, Func<TItem, TKey> keyGetter)
{
    if (collection == null)
    {
        throw new ArgumentNullException(nameof(collection));
    }

    int lower = 0;
    int upper = collection.Count - 1;

    while (lower <= upper)
    {
        int middle = lower + (upper - lower) / 2;
        int comparisonResult = comparer.Compare(keyToFind, keyGetter.Invoke(collection[middle]));
        if (comparisonResult == 0)
        {
            return middle;
        }
        else if (comparisonResult < 0)
        {
            upper = middle - 1;
        }
        else
        {
            lower = middle + 1;
        }
    }

    // If we cannot find the item, return the item below it, so the new item will be inserted next.
    return lower;
}
Contango
  • 76,540
  • 58
  • 260
  • 305
15

This is an ObservableCollection<T>, that automatically sorts itself upon a change, triggers a sort only when necessary, and only triggers a single move collection change action.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;

namespace ConsoleApp4
{
  using static Console;

  public class SortableObservableCollection<T> : ObservableCollection<T>
  {
    public Func<T, object> SortingSelector { get; set; }
    public bool Descending { get; set; }
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
      base.OnCollectionChanged(e);
      if (SortingSelector == null 
          || e.Action == NotifyCollectionChangedAction.Remove
          || e.Action == NotifyCollectionChangedAction.Reset)
        return;
                      
      var query = this
        .Select((item, index) => (Item: item, Index: index));
      query = Descending
        ? query.OrderByDescending(tuple => SortingSelector(tuple.Item))
        : query.OrderBy(tuple => SortingSelector(tuple.Item));

      var map = query.Select((tuple, index) => (OldIndex:tuple.Index, NewIndex:index))
       .Where(o => o.OldIndex != o.NewIndex);

      using (var enumerator = map.GetEnumerator())
       if (enumerator.MoveNext())
          Move(enumerator.Current.OldIndex, enumerator.Current.NewIndex);


    }
  }

  
  //USAGE
  class Program
  {
    static void Main(string[] args)
    {
      var xx = new SortableObservableCollection<int>() { SortingSelector = i => i };
      xx.CollectionChanged += (sender, e) =>
       WriteLine($"action: {e.Action}, oldIndex:{e.OldStartingIndex},"
         + " newIndex:{e.NewStartingIndex}, newValue: {xx[e.NewStartingIndex]}");

      xx.Add(10);
      xx.Add(8);
      xx.Add(45);
      xx.Add(0);
      xx.Add(100);
      xx.Add(-800);
      xx.Add(4857);
      xx.Add(-1);

      foreach (var item in xx)
        Write($"{item}, ");
    }
  }
}

Output:

action: Add, oldIndex:-1, newIndex:0, newValue: 10
action: Add, oldIndex:-1, newIndex:1, newValue: 8
action: Move, oldIndex:1, newIndex:0, newValue: 8
action: Add, oldIndex:-1, newIndex:2, newValue: 45
action: Add, oldIndex:-1, newIndex:3, newValue: 0
action: Move, oldIndex:3, newIndex:0, newValue: 0
action: Add, oldIndex:-1, newIndex:4, newValue: 100
action: Add, oldIndex:-1, newIndex:5, newValue: -800
action: Move, oldIndex:5, newIndex:0, newValue: -800
action: Add, oldIndex:-1, newIndex:6, newValue: 4857
action: Add, oldIndex:-1, newIndex:7, newValue: -1
action: Move, oldIndex:7, newIndex:1, newValue: -1
-800, -1, 0, 8, 10, 45, 100, 4857,
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
  • Descending seems to be implemented reverse. Wonder how the test did the right output. – Thom Hubers Feb 15 '19 at 11:48
  • 2
    Good solution, works great in the real time! Few comments: Descending is reversed (need to fix); to whom don't want to install Nuget for Tuples just replace "(Item: item, Index: index));" -> "new { Index = index, Item = item });" and "(OldIndex: tuple.Index, NewIndex: index))" -> "new { OldIndex = tuple.Index, NewIndex = index })". – uzrgm Apr 17 '19 at 09:39
  • 1
    Nice. If the collection is always sorted, couldn't we skip on the `Remove` notification as well? – Simon Mourier Jul 31 '19 at 14:16
  • 1
    Great solution, thanks! – Roman Feb 04 '22 at 01:52
  • 1
    This is really genius solution, I love it! – Weissu Mar 15 '22 at 14:10
14

I created an extension method to the ObservableCollection

public static void MySort<TSource,TKey>(this ObservableCollection<TSource> observableCollection, Func<TSource, TKey> keySelector)
    {
        var a = observableCollection.OrderBy(keySelector).ToList();
        observableCollection.Clear();
        foreach(var b in a)
        {
            observableCollection.Add(b);
        }
    }

It seems to work and you don't need to implement IComparable

Lance
  • 251
  • 5
  • 13
  • 2
    While this seems to be way faster than moving all items around, it also triggers different events (remove and add instead of move). This might not be an issue depending on the use-case but is definitely a thing to keep in mind. – Tim Pohlmann Jun 13 '19 at 15:17
4

The argument to OrderByDescending is a function returning a key to sort with. In your case, the key is the string itself:

var result = _animals.OrderByDescending(a => a);

If you wanted to sort by length for example, you'll write:

var result = _animals.OrderByDescending(a => a.Length);
manji
  • 47,442
  • 5
  • 96
  • 103
4
myObservableCollection.ToList().Sort((x, y) => x.Property.CompareTo(y.Property));
3
_animals.OrderByDescending(a => a.<what_is_here_?>);

If animals would be a list of object Animal, you could use a property to order the list.

public class Animal
{
    public int ID {get; set;}
    public string Name {get; set;}
    ...
}

ObservableCollection<Animal> animals = ...
animals = animals.OrderByDescending(a => a.Name);
  • 5
    Please note that the order of items in **collection is not modified** after the `OrderByDescending()` method call. – Sergey Vyacheslavovich Brunov Oct 01 '13 at 09:54
  • @SergeyBrunov: Exactly: you should say: animals = animals.OrderByDescending(a => a.Name); – Bram Van Strydonck Oct 01 '13 at 09:58
  • 1
    I get a Cannot convert ObservableCollection to OrderedObservableCollection error. – poudigne Apr 20 '16 at 13:04
  • 1
    This doesn't work. It's not possible to assign ordered collection to ObservableCollection, because type after sorting will be IOrderedEnumerable, you'll get error. – M. Pipal Jun 03 '16 at 13:03
  • 2
    `animals = new ObservableCollection(animals.OrderByDescending(a => a.Name));` This will get around the IOrderedEnumerable error. – Travis Sep 07 '16 at 15:14
  • @Travis Not bad-it does work to get rid of the error, but in WPF, where you have to bind to the `ObservableCollection`, I tried this and it stopped recognizing my collection-it stopped allowing me to add things to the collection and have the changes reflect in the GUI. Binding broke. Probably better would be to assign the sorted collection to another one, then add it back as `ObservableCollection` and not `IOrderedEnumerable` to `animals`, as it will need to be converted from. I went with Marco's solution, instead, though – vapcguy Jan 19 '17 at 17:46
1
/// <summary>
/// Sorts the collection.
/// </summary>
/// <typeparam name="T">The type of the elements of the collection.</typeparam>
/// <param name="collection">The collection to sort.</param>
/// <param name="comparison">The comparison used for sorting.</param>
public static void Sort<T>(this ObservableCollection<T> collection, Comparison<T> comparison = null)
{
    var sortableList = new List<T>(collection);
    if (comparison == null)
        sortableList.Sort();
    else
        sortableList.Sort(comparison);

    for (var i = 0; i < sortableList.Count; i++)
    {
        var oldIndex = collection.IndexOf(sortableList[i]);
        var newIndex = i;
        if (oldIndex != newIndex)
            collection.Move(oldIndex, newIndex);
    }
}

This solution is based on Marco's answer. I had some problems with his solution and therefore improved it by only calling Move if the index actually changed. This should improve performance and also fix the linked issue.

Community
  • 1
  • 1
Tim Pohlmann
  • 4,140
  • 3
  • 32
  • 61
  • 1
    Too damned slow for any reasonable data set. – Quark Soup Jun 13 '19 at 00:21
  • @DonaldAirey fair, did you find a better solution? – Tim Pohlmann Jun 13 '19 at 07:17
  • Yes, the solution by John Leone worked in about 15 ms where this one took 600 ms. – Quark Soup Jun 13 '19 at 13:53
  • @DonaldAirey cool. Does removing and re-adding all items have any unwanted side-effects? – Tim Pohlmann Jun 13 '19 at 15:14
  • I've got a fairly complex MVVM trading blotter with 500 rows in it. Virtualization, lots of binding to the underlying view model. The only difference I've noticed is one sorts instantly, the other has a noticeable lag. – Quark Soup Jun 13 '19 at 15:38
  • @Quarkly while this approach is slower, this seems to me as more correct. When you do a sort operation like John's answer, it raises a Reset event and then a bunch of Adds. While this raises Move events.. – nawfal Aug 10 '20 at 16:58
  • But this sort is unstable. I would assume these are problematic in UI contexts (which is were such bindable collections are mostly used). John's answer is stable. – nawfal Aug 11 '20 at 01:02
  • @nawfal what do you mean with unstable? – Tim Pohlmann Aug 18 '20 at 08:59
  • 1
    @TimPohlmann List.Sort is unstable. See https://en.wikipedia.org/wiki/Category:Stable_sorts for more – nawfal Aug 18 '20 at 09:46
1

I would like to share my thoughts as well, since I've bumped into the same issue.

Well, just answering the question would be:

1 - Add an extenssion to the observable collection class like this:

namespace YourNameSpace
{
    public static class ObservableCollectionExtension
    {
        public static void OrderByReference<T>(this ObservableCollection<T> collection, List<T> comparison)
        {
            for (int i = 0; i < comparison.Count; i++)
            {
                if (!comparison.ElementAt(i).Equals(collection.ElementAt(i)))
                    collection.Move(collection.IndexOf(comparison[i]), i);
            }
        }
        
        public static void InsertInPlace<T>(this ObservableCollection<T> collection, List<T> comparison, T item)
        {
            int index = comparison.IndexOf(item);
            comparison.RemoveAt(index);
            collection.OrderByReference(comparison);
            collection.Insert(index, item);
        }
    }
}

2 - Then use it like this:

_animals.OrderByReference(_animals.OrderBy(x => x).ToList());

This changes your ObservableCollection, you can use linq and it doesn't change the bindings!

Extra:

I've extended @Marco and @Contango answers to my own liking. First I thought of using a list directly as the comparison, so you would have this:

public static void OrderByReference<T>(this ObservableCollection<T> collection, List<T> comparison)
{
    for (int i = 0; i < comparison.Count; i++)
    {
        collection.Move(collection.IndexOf(comparison[i]), i);
    }
}

And using like this:

YourObservableCollection.OrderByReference(YourObservableCollection.DoYourLinqOrdering().ToList());

Then I've thought, since this always move everything and triggers the move in the ObservableCollection why not compare if the object is already in there, and this brings what I've put in the begining with the Equals comparator.

Adding the object to the correct place also sounded good, but I wanned a simple way to do it. So I've came up with that:

public static void InsertInPlace<T>(this ObservableCollection<T> collection, List<T> comparison, T item)
{
    collection.Insert(comparison.IndexOf(item), item);
}

You send a list with the new object where you want and also this new object, so you need to create a list, then add this new object, like this:

var YourList = YourObservableCollection.ToList();
var YourObject = new YourClass { ..... };
YourList.Add(YourObject);
YourObservableCollection.InsertInPlace(YourList.DoYourLinqOrdering().ToList(), YourObject);

But since the ObservableCollection could be in a different order than the list because of the selection in the "DoYourLinqOrdering()" (this would happen if the collection wasn't previously ordered) I've added the first extession (OrderByReference) in the insert as you can see in the begining of the answer. It will not take long if it doesn't need to move the itens arround, so I did't saw a problem in using it.

As performance goes, I've compared the methods by checking the time it takes for each to finish, so not ideal, but anyway, I've tested an observable collection with 20000 itens. For the OrderByReference I didn't saw great difference in the performance by adding the Equal object checker, but if not all itens need to be moved it is faster and it doesn't fire unecessary Move events on the collecitonChanged, so thats something. For the InsertInPlace is the same thing, if the ObservableCollection is already sorted, just checking if the objects are in the right place is faster than moving all the itens around, so there was not a huge difference in time if it is just passing through the Equals statement and you get the benefit of being sure everything is where it should be.

Be aware that if you use this extession with objects that dont mach or with a list that have more or less objects you will get an ArgumentOutOfRangeException or some other unexpect behaviour.

Hopes this helps somebody!

0

I did a sort on a certain class field (distance).

public class RateInfo 
{
    public string begin { get; set; }
    public string end { get; set; }
    public string price { get; set; }
    public string comment { get; set; }
    public string phone { get; set; }
    public string ImagePath { get; set; }
    public string what { get; set; }
    public string distance { get; set; }
}    

public ObservableCollection<RateInfo> Phones { get; set; }

public List<RateInfo> LRate { get; set; }

public ObservableCollection<RateInfo> Phones { get; set; }

public List<RateInfo> LRate { get; set; }

......

foreach (var item in ph)
        {

            LRate.Add(new RateInfo { begin = item["begin"].ToString(), end = item["end"].ToString(), price = item["price"].ToString(), distance=kilom, ImagePath = "chel.png" });
        }

       LRate.Sort((x, y) => x.distance.CompareTo(y.distance));

        foreach (var item in LRate)
        {
            Phones.Add(item);
        }
Neil
  • 11,059
  • 3
  • 31
  • 56
Kairat Koibagarov
  • 1,385
  • 15
  • 9
0

Here is a slight variation on Shimmy's one for collection of classes that already implement the well-known IComparable<T> interface. In this case, the "order by" selector is implicit.

public class SortedObservableCollection<T> : ObservableCollection<T> where T : IComparable<T>
{
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        base.OnCollectionChanged(e);
        if (e.Action != NotifyCollectionChangedAction.Reset &&
            e.Action != NotifyCollectionChangedAction.Move &&
            e.Action != NotifyCollectionChangedAction.Remove)
        {
            var query = this.Select((item, index) => (Item: item, Index: index)).OrderBy(tuple => tuple.Item, Comparer.Default);
            var map = query.Select((tuple, index) => (OldIndex: tuple.Index, NewIndex: index)).Where(o => o.OldIndex != o.NewIndex);
            using (var enumerator = map.GetEnumerator())
            {
                if (enumerator.MoveNext())
                {
                    base.MoveItem(enumerator.Current.OldIndex, enumerator.Current.NewIndex);
                }
            }
        }
    }

    // (optional) user is not allowed to move items in a sorted collection
    protected override void MoveItem(int oldIndex, int newIndex) => throw new InvalidOperationException();
    protected override void SetItem(int index, T item) => throw new InvalidOperationException();

    private class Comparer : IComparer<T>
    {
        public static readonly Comparer Default = new Comparer();

        public int Compare(T x, T y) => x.CompareTo(y);
    }

    // explicit sort; sometimes needed.
    public virtual void Sort()
    {
        if (Items.Count <= 1)
            return;

        var items = Items.ToList();
        Items.Clear();
        items.Sort();
        foreach (var item in items)
        {
            Items.Add(item);
        }
        OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
}
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
0

If performance is your main concern and you dont mind listening to different events, then this is the way to go for a stable sort:

public static void Sort<T>(this ObservableCollection<T> list) where T : IComparable<T>
{
    int i = 0;
    foreach (var item in list.OrderBy(x => x))
    {
        if (!item.Equals(list[i]))
        {
            list[i] = item;
        }

        i++;
    }
}

I am not sure if there is anything simpler and faster (at least theoretically), as far as stable sorts go. Doing a ToArray on the ordered list might make the enumeration faster but at worse space complexity. You could also do away with the Equals check to go even faster, but I guess reducing change notification is a welcome thing.

Also this doesn't break any bindings.

Mind you this raises a bunch of Replace events rather than Move (which is more expected for a Sort operation), and also the number of events raised will be most likely more when compared to other Move approaches in this thread but it is unlikely it matters for performance, I think.. Most UI elements must have implemented IList and doing a replace on ILists should be faster than Moves. But more changed events means more screen refreshes. You will have to test it out to see the implications.


For a Move answer, see this. Haven't seen a more correct implementation that works even when you have duplicates in the collection.

nawfal
  • 70,104
  • 56
  • 326
  • 368
0
    public static ObservableCollection<T> Sort<T>(this ObservableCollection<T> collection, Comparison<T> comparison)
    {
        var sortableList = new List<T>(collection);
        sortableList.Sort(comparison);
        collection.Clear();
        sortableList.ForEach(item => { collection.Add(item); });
        return collection;
    }
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 08 '22 at 02:15