Gözlemlenebilir bir koleksiyonu nasıl sıralayabilirim?


97

Aşağıdaki bir sınıfım var:

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

Bir ObservableCollection koyduğum şey:

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

S: Bunu anahtara göre nasıl sıralayabilirim?


Sınıf içinde bir sıralama uygulaması mı arıyorsunuz yoksa herhangi bir sıralama türü işe yarar mı?
okw

Bunu nasıl anlayacağımı bilmiyorum. Temelde sadece sıralanmasını istiyorum, koleksiyon çok büyük olmayacak (en fazla 20 öğe), bu yüzden her şey işe yarayacak (büyük olasılıkla)
Maciek

WPF çözümü için buna bakın stackoverflow.com/questions/1945461/…
Gayot Fow

Bu sayfadaki yanıtlara bakın: Bazı kritik ve temel işlevler için 22'den fazla yanıt aldığında bozuk bir API'nin çok açık göstergesi.
Gerry

Yanıtlar:


21

Gözlemlenebilir bir nesnenin sıralanması ve sıralanan aynı nesnenin döndürülmesi, bir uzantı yöntemi kullanılarak yapılabilir. Daha büyük koleksiyonlar için, değişen koleksiyon bildirimlerinin sayısına dikkat edin.

Performansı artırmak ve kopyaları işlemek için kodumu güncelledim (orijinal veri örneğinde iyi çalışmasına rağmen orijinalin düşük performansını vurguladığı için nawfal'a teşekkürler). Gözlemlenebilir, soldan sıralanmış bir yarıya ve sağdan sıralanmamış bir yarıya bölünür; burada minimum öğe (sıralı listede bulunduğu gibi), sıralanmamış bölümden sıralı bölümün sonuna kaydırılır. En kötü durum O (n). Esasen bir seçim sıralaması (Çıktı için aşağıya bakın).

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

        int ptr = 0;
        while (ptr < sorted.Count - 1)
        {
            if (!collection[ptr].Equals(sorted[ptr]))
            {
                int idx = search(collection, ptr+1, sorted[ptr]);
                collection.Move(idx, ptr);
            }
            
            ptr++;
        }
    }

    public static int search<T>(ObservableCollection<T> collection, int startIndex, T other)
            {
                for (int i = startIndex; i < collection.Count; i++)
                {
                    if (other.Equals(collection[i]))
                        return i;
                }
    
                return -1; // decide how to handle error case
            }

kullanım: Bir gözlemci ile örnekleme (basit tutmak için bir Kişi sınıfı kullandı)

    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 = "Alice", Age = 39},
                    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 = 5},
                    new Person {Name = "Kim", Age = 19}
                };
    
                //what do observers see?
            
    
observable.CollectionChanged += (sender, e) =>
        {
            Console.WriteLine(
                e.OldItems[0] + " move from " + e.OldStartingIndex + " to " + e.NewStartingIndex);
            int i = 0;
            foreach (var person in sender as ObservableCollection<Person>)
            {
                if (i == e.NewStartingIndex)
                {
                    Console.Write("(" + (person as Person).Age + "),");
                }
                else
                {
                    Console.Write((person as Person).Age + ",");
                }
                
                i++;
            }

            Console.WriteLine();
        };

Koleksiyonun nasıl özetlendiğini gösteren sıralama ilerlemesinin ayrıntıları:

Sue aged 5 move from 8 to 0
(5),51,12,13,39,14,41,20,39,19,
Jack aged 12 move from 2 to 1
5,(12),51,13,39,14,41,20,39,19,
Bob aged 13 move from 3 to 2
5,12,(13),51,39,14,41,20,39,19,
John aged 14 move from 5 to 3
5,12,13,(14),51,39,41,20,39,19,
Kim aged 19 move from 9 to 4
5,12,13,14,(19),51,39,41,20,39,
Jane aged 20 move from 8 to 5
5,12,13,14,19,(20),51,39,41,39,
Alice aged 39 move from 7 to 6
5,12,13,14,19,20,(39),51,41,39,
Jim aged 39 move from 9 to 7
5,12,13,14,19,20,39,(39),51,41,
Mary aged 41 move from 9 to 8
5,12,13,14,19,20,39,39,(41),51,

Person sınıfı, hem IComparable hem de IEquatable'ı uygular; ikincisi, toplanan değişiklik bildirimlerinin sayısını azaltmak için koleksiyondaki değişiklikleri en aza indirmek için kullanılır.

  • DÜZENLE Yeni bir kopya oluşturmadan aynı koleksiyonu sıralar *

Bir ObservableCollection döndürmek için, .ToObservableCollection öğesini * sıralıOC * kullanarak örneğin [bu uygulama] [1] kullanarak çağırın.

**** orig cevap - bu yeni bir koleksiyon oluşturur **** Aşağıdaki doSort yönteminde gösterildiği gibi linq'i kullanabilirsiniz. Hızlı bir kod parçacığı: üretir

3: xey 6: fty 7: aaa

Alternatif olarak, koleksiyonun kendisinde bir uzantı yöntemi kullanabilirsiniz.

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

Bunu buldum ve çok yardımcı oldu. SıralanmışOC varını oluşturan LINQ mudur?
Jason94

9
Size sıralı bir ObservableCollection vermediği için bu cevabın hayranı değilim.
xr280xr

63
-1 o sıralamak etmediğinden , ancak bunun yerine yeni bir koleksiyon oluşturur. ObservableCollection
Kos

2
Güncellenen kod çalışacak, ancak O (n ^ 2) zaman karmaşıklığına sahip olacaktır. Bu, BinarySearchyerine kullanılarak O (n * log (n)) olacak şekilde geliştirilebilir IndexOf.
William Morrison

2
Mükemmel çözüm! ObservableCollection <T> 'den miras alanlar için, RemoveAt ve Insert yöntemlerini kullanmak yerine korumalı MoveItem () yöntemini kullanmak mümkündür. Ayrıca bakınız: referenceource.microsoft.com/#system/compmod/system/…
Herman Cordes

85

Bu basit uzantı benim için çok güzel çalıştı. Sadece emin olmak zorunda MyObjectidi IComparable. Sıralama yöntem gözlemlenebilir koleksiyonunda çağrıldığında MyObjects, CompareToüzerinde yöntem MyObjectbenim Mantıksal Sıralama yöntemini çağıran, hangi denir. Burada yayınlanan geri kalan cevapların tüm çanları ve ıslıkları olmasa da, tam olarak ihtiyacım olan şey buydu.

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

7
cevap bu olmalı
thumbmunkeys

1
Yukarıdaki yanıt, kabul edilen yanıt olduğu için güncellendi ve koleksiyondaki her şey için değişiklik bildirimlerini artıran bu yanıt üzerindeki performans iyileştirmesini ele alıyor
Andrew

3
Mükemmel cevap. return Utils.LogicalStringCompare(a.Title, b.Title);Bunun yerine kullanmanızın bir nedeni var return string.Compare(a.Title, b.Title);mı? @NeilW
Joe

2
@Joe, standart bir dizge karşılaştırması yerine mantıksal bir karşılaştırma yapmam gerekiyordu, bu yüzden uzantıyı ilk etapta yazmam gerekiyordu. Mantıksal dize karşılaştırması, dizelerdeki sayıları dizeler gibi sıralamak yerine doğru sıralar (1, 1000, 2, 20, vb.
Yerine

4
bu kesinlikle gidilecek yol. LINQ'nun tipik olarak yaptığı gibi, IComparable kullanmak yerine bir keySelector'a geçmenize izin vererek, bunu genişletmek için kendi yanıtımı ekledim.
Jonesopolis

39

Buradakilerden daha iyi bir cevap veren alakalı bir blog girişi buldum:

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

GÜNCELLEME

ObservableSortedList Açıklamalarda @romkyns noktaları otomatik sıralama düzeni koruduğunu.

Öğelerini sıralı düzende tutan gözlemlenebilir bir koleksiyon uygular. Özellikle, sipariş değişikliklerine neden olan öğe özelliklerinde yapılan değişiklikler doğru şekilde işlenir.

Ancak şunu da not edin:

İlgili arayüzün karşılaştırmalı karmaşıklığı ve nispeten zayıf dokümantasyonu nedeniyle hatalı olabilir (bkz. Https://stackoverflow.com/a/5883947/33080 ).


2
Aslında bu blog daha kullanışlıdır. Bununla birlikte, öğeler eklendikçe ve kaldırıldıkça sıralanmasını koruyan gözlemlenebilir bir koleksiyona sahip olma sorusuna henüz doğru bir yanıt bulamadım. Kendi kendime yazacağım sanırım.
Stephen Drew

@Steve Deneyebilirsin bu bir .
Roman Starkov

Bağlantı için teşekkürler, bu en basit çözüm gibi göründüğü için uzatma yöntemine gittim. Bir cazibe çalışıyor: D
pengibot

bw kimse blogda html dosya adında bir yazım hatası olduğunu fark etti mi (obversablecollection)? : P
laishiekai

1
@romkyns cevabı ObservableCollection <T> alanını genişletmekti. GridView bunu gayet iyi tanıyor. Sonra, tıpkı sizin yaptığınız gibi, yöntemlerini saklayın. Zamanım olduğunda tam bir çözüm yayınlayacağım.
Weston

25

Bu basit yöntemi kullanabilirsiniz:

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

Şu şekilde sıralayabilirsiniz:

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

Daha fazla ayrıntı: http://jaider.net/2011-05-04/sort-a-observablecollection/


4
Bu, ObservableCollection'ı temizler ve ardından tüm nesneleri yeniden ekler - bu nedenle, UI'niz koleksiyona bağlıysa, animasyonlu değişiklikleri görmeyeceğinizi, örneğin öğeler taşındığında
Carlos P

1
Neden etrafta dolaşan öğeleri görüntülemeniz gerektiğinden emin değilim ... örneğin, normalde ObservableCollectionaçılır listelerin ItemSource'una bağlısınız ve koleksiyonu hiç görmüyorsunuz. Ayrıca bu temizleme ve doldurma işlemi son derece hızlıdır ... "yavaş" olan, zaten optimize edilmiş olan sıralama olabilir. son olarak, taşıma yönteminizi uygulamak için bu kodu değiştirebilirsiniz, sortedlistve sourcediğerlerine sahip olmak kolaydır.
Jaider

3
Bir açılır menüye bağlıysanız, etrafta dolaşan öğeleri görmekten yararlanamazsınız, bu doğru. Yine de bir ListBox'a bağlıysanız, WPF veya Silverlight veya Windows Mağazası Uygulamaları gibi çerçeveler, koleksiyondaki nesneler yeniden indekslenirken yararlı görsel geri bildirim sağlayacaktır.
Carlos P

Bu, Taşı yaklaşımından daha hızlı olsa da, bir dizi Sıfırlama / Ekleme olayını ortaya çıkarır. En yüksek oyu alan cevap (Harekete geçme yaklaşımı) bunu en aza indirir ve doğru bir şekilde Moveolayları gündeme getirir, bu da yalnızca gerçekten hareket edenler için.
nawfal

19

WPF , ListCollectionViewsınıfını kullanarak kullanıma hazır canlı sıralama sağlar ...

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

Bu başlatma tamamlandıktan sonra yapacak başka bir şey yok. Pasif bir sıralamaya göre avantajı, ListCollectionView'un tüm ağır işleri geliştiriciye şeffaf bir şekilde yapmasıdır. Yeni öğeler otomatik olarak doğru sıralama düzenine göre yerleştirilir. IComparerT'den türetilen herhangi bir sınıf , özel sıralama özelliği için uygundur.

Belgeler ve diğer özellikler için ListCollectionView'e bakın .


6
gerçekten işe yaradı: D, bu kadar basit bir görev için diğer "aşırı mühendislik" çözümünden çok daha iyi bir çözüm.
MushyPeas

Blogunuz nereye gitti?
phoog

"Şeffaf" şeylerle ilgili sorun, işe yaramadığında nereye bakacağınızı görememenizdir. Microsoft'un belgelerinde% 100 şeffaf bir örnek var, yani onu hiç göremiyorsunuz.
Paul McCarthy

15

Yukarıdaki "Richie" blogundaki balon sıralama uzantı yöntemi yaklaşımını beğendim, ancak yalnızca tüm nesneyi karşılaştırarak sıralamak istemiyorum. Daha sık olarak nesnenin belirli bir özelliğine göre sıralamak istiyorum. Bu yüzden, OrderBy'nin yaptığı gibi bir anahtar seçiciyi kabul edecek şekilde değiştirdim, böylece hangi özelliği sıralayacağınızı seçebilirsiniz:

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

Yeni bir koleksiyon döndürmek yerine ObservableCollection öğenizin mevcut örneğini sıralaması dışında, OrderBy'yi çağırdığınız şekilde çağırırsınız:

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

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

1
Bunu yayınladığınız için teşekkürler - Richie'nin blogundaki yorumlarda belirtildiği gibi, bu kodda bazı önemli geliştirmeler var; özellikle kaynağın 'Taşı' yöntemini kullanarak. Sanırım bu Kaldır / Ekle satırlarını source.Move (j-1, j) ile değiştirir;
Carlos P

2
Bu sıralama algoritması optimize edilmemiştir en.wikipedia.org/wiki/Sorting_algorithm
Jaider

@Jaider Evet, optimize edilmiş, sadece genel ham hız için değil.
jv42

Bu, bir dizi Kaldır / Ekle olayını ortaya çıkarır (inanıyorum ki her N için) .. En yüksek oyu alan cevap bunu en aza indirir ve doğru bir şekilde Taşı olaylarını yükseltir, bu da sadece gerçekten taşınanlar için. Buradaki anahtar, hemen yerinde sıralama yapmamak, bunun yerine onu harici olarak sıralamak OrderByve ardından gerçek değişikliği bulmak için bir karşılaştırma yapmaktır.
nawfal

11

@ NielW'ın cevabı, gerçek yerinde sıralama için gitmenin yoludur. Kullanım zorunluluğunu atlamanıza izin veren biraz değiştirilmiş bir çözüm eklemek istedim 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);
    }
}

şimdi bunu herhangi bir LINQ yöntemi gibi çağırabilirsiniz:

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

2
Fazladan bir çikolatalı kurabiye için bir boole parametresi "Artan" ve if(!Ascending) sorted.Reverse();hemen öncesine bir ekleyebilirsiniz for(ve -daha fazla- hafıza konusunda endişelenmenize gerek yok, Reverse yöntemi herhangi bir yeni nesne oluşturmaz, yerinde ters)
Sharky

Test koleksiyonuma göre, Hareket (0,0) bir CollectionChanged olayına yol açar. Bu nedenle, bir hareketin gerekli olup olmadığını kontrol etmek bir performans iyileştirmesi olacaktır.
sa.

10

NeilW'nin cevabına eklemek istiyorum . Siparişe benzeyen bir yöntemi dahil etmek için. Bu yöntemi bir uzantı olarak ekleyin:

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

Ve şu şekilde kullanın:

myCollection = new ObservableCollection<MyObject>();

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

8

Bir varyasyon, bir seçim sıralama algoritması kullanarak koleksiyonu yerinde sıraladığınız yerdir . MoveYöntem kullanılarak elemanlar yerine taşınır . Her hareket, CollectionChangedolayı ile NotifyCollectionChangedAction.Move(ve ayrıca PropertyChangedözellik adıyla Item[]) tetikler .

Bu algoritmanın bazı güzel özellikleri vardır:

  • Algoritma, kararlı bir sıralama olarak uygulanabilir.
  • Koleksiyonda taşınan öğelerin sayısı (örneğin, CollectionChangedtetiklenen olaylar) neredeyse her zaman, ekleme sıralama ve balonla sıralama gibi diğer benzer algoritmalardan daha azdır.

Algoritma oldukça basit. Koleksiyon, daha sonra koleksiyonun başına taşınan en küçük öğeyi bulmak için yinelenir. İşlem, ikinci öğeden başlayarak ve tüm öğeler yerine taşınana kadar devam eder. Algoritma çok verimli değil, ancak bir kullanıcı arayüzünde görüntüleyeceğiniz herhangi bir şey için önemli olmamalı. Ancak taşıma işlemlerinin sayısı açısından oldukça verimlidir.

İşte basitlik açısından elemanların uygulanmasını gerektiren bir genişletme yöntemi IComparable<T>. Diğer seçenekler bir IComparer<T>veya a kullanıyor 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);
    }
  }

}

Bir koleksiyonu sıralamak, uzantı yöntemini çağırmaktan ibarettir:

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

1
Bu, burada açıklananların hepsinden tercih ettiğim sıralama yöntemidir, maalesef Taşı yöntemi Silverlight 5'te kullanılamaz.
Eduardo Brites

1
'Profiler.Profile.ProfileObject' hatası alıyorum, genel tür veya 'ObservableCollectionExtensions.Sort <T> (ObservableCollection <T>)' yönteminde 'T' tür parametresi olarak kullanılamaz. 'Profiler.Profile.ProfileObject' ile 'System.IComparable <Profiler.Profile.ProfileObject>
New Bee

1
@NewBee: Bu eklenti yöntemi belirtir jenerik kısıtlamasını üzerinde Tkoleksiyonunda öğelerini sıralamak mümkün. Sıralama, büyük ve küçük kavramını içerir ve nasıl ProfileObjectsıralanacağını yalnızca siz tanımlayabilirsiniz . Uygulamak için gereken uzatma yöntemini kullanmak için IComparable<ProfileObject>üzerinde ProfileObject. Diğer alternatifler, bir IComparer<ProfileObject>veya a'yı belirterek Func<ProfileObject, ProfileObject, int>ve sıralama kodunu buna göre değiştirerek belirtildiği gibidir.
Martin Liversage

4

Xr280xr cevabındaki genişletme yöntemini biraz iyileştirmek için sıralamanın azalan olup olmadığını belirlemek için isteğe bağlı bir bool parametresi ekledim. Bu cevaba yapılan yoruma Carlos P'nin önerisini de ekledim. Lütfen aşağıya bakın.

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

2

Koleksiyonunuzu her zaman düzenli tutmanız gerekiyor mu? Çiftleri alırken, bunların her zaman sıralanmasına mı ihtiyacınız var, yoksa sadece birkaç kez mi (belki sadece sunum için)? Koleksiyonunuzun ne kadar büyük olmasını bekliyorsunuz? Kullanacağınız cadı yöntemine karar vermenize yardımcı olabilecek birçok faktör vardır.

Koleksiyonun her zaman sıralanmasına ihtiyacınız varsa, öğeleri eklediğinizde veya sildiğinizde bile ve ekleme hızı bir sorun değilse, belki SortedObservableCollection@Gerrie Schenck'in bahsettiği gibi bir tür uygulamanız veya bu uygulamaya göz atmanız gerekir .

Koleksiyonunuzun sadece birkaç kez sıralanması gerekiyorsa şunu kullanın:

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

Bu, koleksiyonu sıralamak biraz zaman alacaktır, ancak yine de, onunla ne yaptığınıza bağlı olarak en iyi çözüm olabilir.


1
Bu yanıttaki bağlantı LGPL lisanslı koddur, bu nedenle Silverlight iseniz (dinamik olarak bağlanamıyorsanız) veya açık kaynak değilseniz bu koda dikkat edin.
yzorg

2

Mevcut cevabım zaten en fazla oyu aldı, ancak bunu yapmanın daha iyi ve daha modern bir yolunu buldum.

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

orijinal cevabı güncellemek daha iyi olmaz mıydı?
Nathan Hughes

Hayır. Zaten diğer yanıtlardan daha fazla oy aldı. İnsanların bunu bu şekilde yapmayı tercih edeceğini varsaymayacağım. Bunu yapmanın başka bir yolunu önereceğimi düşündüm, özellikle de yeni cevaplarda ödül olduğu için.
NielW

1

Yeni bir sınıf oluşturun SortedObservableCollection, onu türetin ObservableCollectionve uygulayın IComparable<Pair<ushort, string>>.


1

Bunun bir yolu, onu bir Listeye dönüştürmek ve ardından bir karşılaştırma temsilcisi sağlamak için Sort () 'u çağırmaktır. Gibi bir şey:-

(test edilmemiş)

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


1

Ne oluyor, hızlı bir şekilde bir araya getirilmiş bir cevap da vereceğim ... buradaki diğer uygulamalara biraz benziyor, ama yine de ekleyeceğim:

(zar zor test edildi, umarım kendimi utandırmıyorum)

Önce bazı hedefleri belirtelim (varsayımlarım):

1) ObservableCollection<T>Bildirimleri vb. Korumak için yerinde sıralanmalıdır .

2) Korkunç derecede verimsiz olmamalıdır (yani, standart "iyi" sıralama verimliliğine yakın bir şey )

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

1

Benim durumumda bu cevapların hiçbiri işe yaramadı. Ya ciltlemeyi mahvettiği için ya da çok fazla ek kodlama gerektirdiği için bir tür kabus ya da cevap kırılmış. İşte düşündüğüm daha basit bir cevap. Çok daha az kod ve bu ek bir this.sort tipi yöntemle aynı gözlemlenebilir koleksiyon olarak kalır. Bunu bu şekilde yapmamam için bir neden varsa bana bildirin (verimlilik vb.)?

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'in benim genel sınıfım olduğu yer. Çok daha basit görünüyordu. Ek fayda: Aslında çalışır ve bağlamalarla uğraşmaz veya yeni bir koleksiyon vb. Döndürmez.


1

Pekala, XAML ile çalışmak için ObservableSortedList'i alma konusunda sorunlar yaşadığım için devam ettim ve SortingObservableCollection'ı yarattım . ObservableCollection'dan miras alır, bu nedenle XAML ile çalışır ve ben onu% 98 kod kapsamına göre test ettim. Bunu kendi uygulamalarımda kullandım, ancak hatasız olduğuna söz vermeyeceğim. Katkıda bulunmaktan çekinmeyin. Örnek kod kullanımı:

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

Bu bir PCL'dir, dolayısıyla Windows Mağazası, Windows Phone ve .NET 4.5.1 ile çalışmalıdır.


1
Muhtemelen newtüm bu yöntemleri kullanmamalısınız, eğer birisi daha genel olarak yazılmış bir örneğe sahipse, bu yöntemler çağrılmayacaktır. Bunun yerine overrideher geçersiz kılınabilen yöntemi ve bunları gerektiği gibi değiştirin veya geri dönün base.Method(...). Örneğin, endişelenmenize bile gerek yok .Addçünkü dahili olarak kullanılıyor .InsertItem, bu nedenle .InsertItemgeçersiz kılınır ve ayarlanırsa, .Addsiparişle uğraşmaz.
HB

1

OC uzantılarıyla yaptığım şey şu:

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

1

Bu benim için çalıştı, uzun zaman önce bir yerde buldum.

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

Kullanım:

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

0

Tek bir şeye göre değil birden fazla şeye göre sıralama yapabilmem gerekiyordu. Bu cevap, diğer cevaplardan bazılarına dayanmaktadır, ancak daha karmaşık bir sıralamaya izin vermektedir.

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

Bunu kullandığınızda, bir dizi OrderBy / ThenBy çağrısı gönderin. Bunun gibi:

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

0

Diğer çözümlerden çok şey öğrendim ama birkaç problem buldum. İlk olarak, bazıları büyük listeler için oldukça yavaş olma eğiliminde olan IndexOf'a bağlıdır. İkincisi, ObservableCollection'ın EF varlıkları vardı ve Remove'u kullanmak bazı yabancı anahtar özelliklerini bozuyor gibi görünüyordu. Belki yanlış bir şey yapıyorum.

Ne olursa olsun, Kaldır / Ekle yerine Bir Taşıma kullanılabilir, ancak bu performans düzeltmesiyle ilgili bazı sorunlara neden olur.

Performans sorununu çözmek için IndexOf sıralanmış değerlerle bir sözlük oluşturuyorum. Sözlüğü güncel tutmak ve varlık özelliklerini korumak için, diğer çözümlerde uygulanan bir yerine iki hareketle uygulanan bir takas kullanın.

Tek bir hareket, öğelerin dizinlerini konumlar arasında kaydırır, bu da IndexOf sözlüğünü geçersiz kılar. Bir takas uygulamak için ikinci bir hareket eklemek konumları geri yükler.

public static void Sort<TSource, TKey>(this ObservableCollection<TSource> collection, Func<TSource, TKey> keySelector)
{
    List<TSource> sorted = collection.OrderBy(keySelector).ToList();
    Dictionary<TSource, int> indexOf = new Dictionary<TSource, int>();

    for (int i = 0; i < sorted.Count; i++)
        indexOf[sorted[i]] = i;

    int idx = 0;
    while (idx < sorted.Count)
        if (!collection[idx].Equals(sorted[idx])) {
            int newIdx = indexOf[collection[idx]]; // where should current item go?
            collection.Move(newIdx, idx); // move whatever's there to current location
            collection.Move(idx + 1, newIdx); // move current item to proper location
        }
        else {
            idx++;
        }
}

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

Oh anladım ... Gayot en çok olumsuz oy alan cevaba ödül vermek istedi lol
NielW

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.