Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
SortedObservableCollection.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.Collections.ObjectModel;
6 using System.Linq;
7 
8 namespace SiliconStudio.Presentation.Collections
9 {
10  /// <summary>
11  /// An observable collection that automatically sorts inserted items using either the default comparer for their type, or a custom provider comparer.
12  /// Insertion and search are both O(log(n)). The method <see cref="BinarySearch"/> must be used for O(log(n)).
13  /// If items are modified outside from the collection and these changes affect the order, the collection won't be updated. You must remove them from the collection before modifying it, and re-add it afterwards.
14  /// Use an <see cref="AutoUpdatingSortedObservableCollection{T}"/> if you want to maintain order after modifying items.
15  /// </summary>
16  /// <typeparam name="T">The type of item this collection contains.</typeparam>
17  /// <seealso cref="AutoUpdatingSortedObservableCollection{T}"/>
18  public class SortedObservableCollection<T> : ObservableCollection<T>, IObservableCollection<T>, IReadOnlyObservableCollection<T>
19  {
20  protected readonly Func<T, T, int> DefaultCompareFunc;
21 
22  /// <summary>
23  /// Public constructor. A comparer can be provided to compare items. If null, the default comparer will be used (if available).
24  /// If no comparer is provided and no default comparer is available, an <see cref="InvalidOperationException"/> will be thrown when methods requesting comparer are invoked.
25  /// </summary>
26  /// <param name="comparer">The comparer to use to compare items.</param>
27  public SortedObservableCollection(IComparer<T> comparer = null)
28  {
29  // Check if a comparer is provided. If so, we will use it for sorting.
30  if (comparer != null)
31  {
32  DefaultCompareFunc = comparer.Compare;
33  }
34 
35  // Otherwise, let's check if the generic type T implements the generic IComparable interface.
36  if (DefaultCompareFunc == null && typeof(T).GetInterfaces().Contains(typeof(IComparable<T>)))
37  {
38  if (typeof(T).IsValueType)
39  {
40  DefaultCompareFunc = (item1, item2) => ((IComparable<T>)item1).CompareTo(item2);
41  }
42  else
43  {
44  DefaultCompareFunc = (item1, item2) =>
45  {
46  // ReSharper disable RedundantCast - If we don't cast, we get a possible compare of a value type with null, which is not the case here
47  if ((object)item1 == null && (object)item2 == null)
48  // ReSharper restore RedundantCast
49  return 0;
50 
51  return (object)item1 != null ? ((IComparable<T>)item1).CompareTo(item2) : -((IComparable<T>)item2).CompareTo(item1);
52  };
53  }
54  }
55 
56  // If we still have no way of comparing objects, let's check for the non-generic IComparable interface.
57  if (DefaultCompareFunc == null && typeof(T).GetInterfaces().Contains(typeof(IComparable)))
58  {
59  if (typeof(T).IsValueType)
60  {
61  DefaultCompareFunc = (item1, item2) => ((IComparable)item1).CompareTo(item2);
62  }
63  else
64  {
65  DefaultCompareFunc = (item1, item2) =>
66  {
67  // ReSharper disable RedundantCast - If we don't cast, we get a possible compare of a value type with null, which is not the case here
68  if ((object)item1 == null && (object)item2 == null)
69  // ReSharper restore RedundantCast
70  return 0;
71 
72  return (object)item1 != null ? ((IComparable)item1).CompareTo(item2) : -((IComparable)item2).CompareTo(item1);
73  };
74  }
75  }
76 
77  // If we have no comparer at this point we're dead
78  if (DefaultCompareFunc == null)
79  {
80  throw new ArgumentException("The type of this collection does not implement any IComparable interface and no IComparer has been provided");
81  }
82  }
83 
84  /// <summary>
85  /// Seach the index of an item.
86  /// </summary>
87  /// <param name="item">The item to search.</param>
88  /// <returns>The index of the item in the collection, or -1 if the item does not exist in the collection.</returns>
89  public int BinarySearch(T item)
90  {
91  return GetIndex(item, false);
92  }
93 
94  /// <summary>
95  /// Search the index of key in the collection, using a provided function to compare items to the key.
96  /// </summary>
97  /// <typeparam name="TSearch">The type of the key to search.</typeparam>
98  /// <param name="key">The key to search in the collection.</param>
99  /// <param name="compareFunc">A comparison function that can compare a key to an item of the collection.</param>
100  /// <returns></returns>
101  public int BinarySearch<TSearch>(TSearch key, Func<T, TSearch, int> compareFunc)
102  {
103  if (compareFunc == null)
104  throw new ArgumentNullException("compareFunc");
105 
106  return GetIndex(key, false, compareFunc);
107  }
108 
109  /// <inheritdoc/>
110  public override string ToString()
111  {
112  return string.Format("{{SortedObservableCollection}} Count = {0}", Count);
113  }
114 
115  protected int GetIndex(T item, bool returnInsertIndex)
116  {
117  if (DefaultCompareFunc == null) throw new InvalidOperationException("No comparison available or provided for items of this collection.");
118  return GetIndex(item, returnInsertIndex, DefaultCompareFunc);
119  }
120 
121  protected int GetIndex<TSearch>(TSearch key, bool returnInsertIndex, Func<T, TSearch, int> compareFunc)
122  {
123  int imin = 0;
124  int imax = Count - 1;
125  while (imax >= imin)
126  {
127  int imid = (imin + imax) / 2;
128 
129  int comp = compareFunc(this[imid], key);
130  if (comp < 0)
131  imin = imid + 1;
132  else if (comp > 0)
133  imax = imid - 1;
134  else
135  return imid;
136  }
137 
138  return returnInsertIndex ? imin : -1;
139  }
140 
141  /// <inheritdoc/>
142  protected override void InsertItem(int index, T item)
143  {
144  int i = GetIndex(item, true);
145  base.InsertItem(i, item);
146  }
147 
148  /// <inheritdoc/>
149  protected override void MoveItem(int oldIndex, int newIndex)
150  {
151  throw new InvalidOperationException("Cannot call MoveItem on a SortedObservableCollection");
152  }
153 
154  protected void ObservableCollectionMoveItem(int oldIndex, int newIndex)
155  {
156  base.MoveItem(oldIndex, newIndex);
157  }
158 
159  /// <inheritdoc/>
160  protected override void SetItem(int index, T item)
161  {
162  throw new InvalidOperationException("Cannot call SetItem on a SortedObservableCollection");
163  }
164  }
165 }
SortedObservableCollection(IComparer< T > comparer=null)
Public constructor. A comparer can be provided to compare items. If null, the default comparer will b...