通过 C# 对 ObservableCollection<string> 进行排序
Posted
技术标签:
【中文标题】通过 C# 对 ObservableCollection<string> 进行排序【英文标题】:Sort ObservableCollection<string> through C# 【发布时间】:2013-10-07 10:05:10 【问题描述】:我有以下ObservableCollection<string>
。我需要按字母顺序排序。
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"
;
我试过_animals.OrderByDescending
。但是不知道怎么正确使用。
_animals.OrderByDescending(a => a.<what_is_here_?>);
我该怎么做?
【问题讨论】:
可能重复:***.com/questions/16562175, ***.com/questions/5803786. 看这里***.com/questions/3973137/… 大多数与Move
相关的答案在集合中有重复项时无法正常工作。请参阅此以获得正确的实现:***.com/a/1945701
【参考方案1】:
简介
基本上,如果需要显示已排序的集合,请考虑使用CollectionViewSource
类:将其Source
属性分配(“绑定”)到源集合——ObservableCollection<T>
类的一个实例。
这个想法是CollectionViewSource
class provides an instance of the CollectionView
class。这是原始(源)集合的一种“投影”,但应用了排序、过滤等。
参考资料:
How to: Sort and Group Data Using a View in XAML. WPF’s CollectionViewSource。实时整形
WPF 4.5 为 CollectionViewSource
引入了“实时整形”功能。
参考资料:
WPF 4.5 New Feature: Live Shaping。 CollectionViewSource.IsLiveSorting Property。 Repositioning data as the data's values change (Live shaping)。解决方案
如果仍然需要对ObservableCollection<T>
类的实例进行排序,可以按照以下方式进行。
ObservableCollection<T>
类本身没有排序方法。但是,可以重新创建集合以对项目进行排序:
// 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));
其他细节
请注意OrderBy()
和OrderByDescending()
方法(与其他LINQ 扩展方法一样)不要修改源集合!他们改为创建一个新序列(即实现IEnumerable<T>
接口的类的新实例)。因此,需要重新创建集合。
【讨论】:
CollectionViewSource 应尽可能使用,但 Xamarin Forms 没有此功能,因此不可能。使用 OrderBy 的第二种解决方案是不可行的,因为假设 UI 正在侦听 observable 集合,因为它实现了 INotifyCollectionChanged。如果您创建一个新实例,那有什么意义呢? UI 将进行完全刷新。 UWP 中的 CollectionViewSource 没有未来。这是一个死胡同。 @Quarkly 推荐的 UWP 解决方案是什么? 我一直在使用 LINQ 对可观察的集合进行排序。对于动画,我实际上已经采取了额外的步骤来对旧集合顺序与新集合顺序执行差异,然后执行最小的 RemoveAt/InsertAt 操作以将它们放置在正确的顺序中。这是很多额外的工作,但它使动画看起来更加自然。【参考方案2】:我知道这是一个老问题,但它是“排序 observablecollection”的第一个谷歌结果,所以认为值得留下我的两分钱。
方式
我将采用的方法是从ObservableCollection<>
开始构建List<>
,对其进行排序(通过其Sort()
方法more on msdn),当List<>
已排序后,重新排序@987654327 @ 使用 Move()
方法。
代码
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);
测试
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]);
注意事项
这只是一个概念证明,展示了如何在不破坏项目绑定的情况下对ObservableCollection<>
进行排序。排序算法有改进和验证的空间(如here 指出的索引检查)。
【讨论】:
注意:根据 MSDN,“ObservableCollection.IndexOf”方法是“一个 O(n) 操作,其中 n 是计数”,我怀疑“ObservableCollection.Move”方法调用了“ObservableCollection。 RemoveAt”和“ObservableCollection.Insert”方法(同样,根据 MSDN)也是“一个 O(n) 操作,其中 n 是 Count”。在下一条评论中继续... 如果运行时间很重要,我怀疑你最好使用排序列表重新创建“ObservableCollection”(即使用“ObservableCollection我看了这些,我正在整理它,然后它打破了绑定,如上所述。想出了这个解决方案,虽然比你的大多数人都简单,但它似乎可以做我想做的事,,,
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;
【讨论】:
这远远优于其他解决方案。 #1 它不会破坏绑定,#2,它不依赖于 CollectionViewSource 这是一个过时的模式,#3 它很简单,#4 与“移动项目”相比,它的速度非常快解决方案。 虽然这似乎比移动所有项目要快得多,但它也会触发不同的事件(删除和添加而不是移动)。根据用例,这可能不是问题,但绝对是要记住的事情。 @TimPohlmann - 移动也会触发事件,所以区别在于使用“移动”算法的 O 操作与使用清除和添加算法的 O + 1 操作。 @DonaldAirey 我说的是事件是由不同的事件参数内容触发的。根据您的事件处理程序正在侦听的内容,这可能会导致一些问题。【参考方案4】:我为 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);
它似乎工作,你不需要实现 IComparable
【讨论】:
虽然这似乎比移动所有项目要快得多,但它也会触发不同的事件(删除和添加而不是移动)。根据用例,这可能不是问题,但绝对是要记住的事情。【参考方案5】:这是一个ObservableCollection<T>
,它会在更改时自动排序,仅在必要时触发排序,并且仅触发单个移动集合更改操作。
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.OrderBy(tuple => SortingSelector(tuple.Item))
: query.OrderByDescending(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, ");
输出:
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,
【讨论】:
降序似乎是反向实现的。想知道测试如何做出正确的输出。 很好的解决方案,实时效果很好! Few cmets:降序反转(需要修复);不想为元组安装 Nuget 的人只需替换“(项目:项目,索引:索引));” ->“新索引=索引,项目=项目);”和 "(OldIndex: tuple.Index, NewIndex: index))" -> "new OldIndex = tuple.Index, NewIndex = index )"。 不错。如果集合总是排序的,我们不能跳过Remove
通知吗?
很好的解决方案,谢谢!【参考方案6】:
这种扩展方法无需对整个列表进行排序。
相反,它将每个新项目插入到位。因此列表始终保持排序状态。
事实证明,这种方法只适用于由于集合更改时缺少通知而导致许多其他方法失败的情况。而且速度相当快。
下面的代码应该是防弹的;它已经在大规模生产环境中进行了广泛的测试。
使用方法:
// 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.
以及扩展方法:
/// <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);
以及二分查找扩展方法:
/// <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;
【讨论】:
【参考方案7】:myObservableCollection.ToList().Sort((x, y) => x.Property.CompareTo(y.Property));
【讨论】:
【参考方案8】:OrderByDescending
的参数是一个返回要排序的键的函数。在您的情况下,关键是字符串本身:
var result = _animals.OrderByDescending(a => a);
例如,如果你想按长度排序,你会写:
var result = _animals.OrderByDescending(a => a.Length);
【讨论】:
请注意OrderByDescending()
方法调用后collection中的item的顺序没有被修改。
由于@Sergey Brunov 的解释,它无法正常工作。您需要将结果保存在另一个变量中。【参考方案9】:
_animals.OrderByDescending(a => a.<what_is_here_?>);
如果animals 是对象Animal 的列表,您可以使用属性对列表进行排序。
public class Animal
public int ID get; set;
public string Name get; set;
...
ObservableCollection<Animal> animals = ...
animals = animals.OrderByDescending(a => a.Name);
【讨论】:
请注意在OrderByDescending()
方法调用之后collection中的item顺序没有被修改。
@SergeyBrunov:确切地说:你应该说:animals = animals.OrderByDescending(a => a.Name);
我收到无法将 ObservableCollection 转换为 OrderedObservableCollection 错误。
这不起作用。无法将有序集合分配给 ObservableCollectionanimals = new ObservableCollection<Animal>(animals.OrderByDescending(a => a.Name));
这将绕过 IOrderedEnumerable 错误。【参考方案10】:
/// <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);
此解决方案基于Marco's answer。我有some problems 和他的解决方案,因此如果索引实际发生变化,只调用Move
来改进它。这应该会提高性能并解决相关问题。
【讨论】:
对于任何合理的数据集来说都太慢了。 @DonaldAirey fair,你找到更好的解决方案了吗? 是的,John Leone 的解决方案用了大约 15 毫秒,而这个用了 600 毫秒。 @DonaldAirey 很酷。删除和重新添加所有项目是否有任何不需要的副作用? @TimPohlmann List我也想分享我的想法,因为我遇到了同样的问题。
好吧,只要回答这个问题就是:
1 - 像这样向 observable 集合类添加扩展:
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 - 然后像这样使用它:
_animals.OrderByReference(_animals.OrderBy(x => x).ToList());
这会更改您的 ObservableCollection,您可以使用 linq,但不会更改绑定!
额外:
我已经根据自己的喜好扩展了@Marco 和@Contango 的答案。首先我想到了直接使用列表作为比较,所以你会这样:
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);
并像这样使用:
YourObservableCollection.OrderByReference(YourObservableCollection.DoYourLinqOrdering().ToList());
然后我想,既然这总是移动所有内容并触发 ObservableCollection 中的移动,为什么不比较对象是否已经在其中,这带来了我在 Equals 比较器开始时提出的内容。
将对象添加到正确的位置听起来也不错,但我想要一种简单的方法来做到这一点。所以我想出了这个:
public static void InsertInPlace<T>(this ObservableCollection<T> collection, List<T> comparison, T item)
collection.Insert(comparison.IndexOf(item), item);
您发送一个列表,其中包含您想要的新对象以及这个新对象,因此您需要创建一个列表,然后添加这个新对象,如下所示:
var YourList = YourObservableCollection.ToList();
var YourObject = new YourClass ..... ;
YourList.Add(YourObject);
YourObservableCollection.InsertInPlace(YourList.DoYourLinqOrdering().ToList(), YourObject);
但是,由于“DoYourLinqOrdering()”中的选择,ObservableCollection 的顺序可能与列表的顺序不同(如果集合之前没有排序,则会发生这种情况)我添加了第一个扩展(OrderByReference)在插入中,您可以在答案的开头看到。如果不需要移动它,它不会花费很长时间,所以我没有看到使用它的问题。
随着性能的发展,我通过检查每个方法完成所需的时间来比较这些方法,所以并不理想,但无论如何,我已经用 20000 个元素测试了一个可观察的集合。对于 OrderByReference,通过添加 Equal 对象检查器,我没有看到性能上有很大差异,但如果不是所有的元素都需要移动,它会更快,并且不会在 collecitonChanged 上触发不必要的 Move 事件,所以就是这样。对于InsertInPlace也是一样的,如果ObservableCollection已经排好序了,仅仅检查对象是否在正确的位置比移动所有的itens要快,所以如果只是通过Equals 语句,您可以确保一切都在其应有的位置。
请注意,如果您将此扩展与不匹配的对象或包含更多或更少对象的列表一起使用,您将收到 ArgumentOutOfRangeException 或其他一些意外行为。
希望这对某人有所帮助!
【讨论】:
【参考方案12】:我对某个类字段(距离)进行了排序。
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);
【讨论】:
【参考方案13】:这里是 Shimmy 的一个细微变化,用于收集已经实现了众所周知的 IComparable<T>
interface 的类。在这种情况下,“order by”选择器是隐式的。
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));
【讨论】:
【参考方案14】:如果性能是您的主要关注点并且您不介意聆听不同的事件,那么这是实现稳定排序的方法:
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++;
就稳定排序而言,我不确定是否有任何更简单和更快的方法(至少在理论上)。在有序列表上执行 ToArray 可能会使枚举更快,但空间复杂度更差。您也可以取消 Equals
检查以更快,但我想减少更改通知是一件受欢迎的事情。
这也不会破坏任何绑定。
请注意,这会引发一堆 Replace
事件而不是 Move(这对于 Sort 操作来说更值得期待),并且与此线程中的其他 Move 方法相比,引发的事件数量很可能会更多,但是我认为这不太可能对性能很重要。大多数 UI 元素必须已经实现 IList
并且在 ILists
上进行替换应该比 Moves 更快。但更多更改的事件意味着更多的屏幕刷新。您必须对其进行测试才能看到影响。
对于Move
的答案,see this。即使您在集合中有重复项,也没有看到更正确的实现。
【讨论】:
以上是关于通过 C# 对 ObservableCollection<string> 进行排序的主要内容,如果未能解决你的问题,请参考以下文章
通过 C# 对 ObservableCollection<string> 进行排序
有没有啥方法直接通过C#控制CAD而不是采用dll方式对CAD二次开发的