ReactiveCollection.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. namespace UniRx
  5. {
  6. public struct CollectionAddEvent<T> : IEquatable<CollectionAddEvent<T>>
  7. {
  8. public int Index { get; private set; }
  9. public T Value { get; private set; }
  10. public CollectionAddEvent(int index, T value)
  11. :this()
  12. {
  13. Index = index;
  14. Value = value;
  15. }
  16. public override string ToString()
  17. {
  18. return string.Format("Index:{0} Value:{1}", Index, Value);
  19. }
  20. public override int GetHashCode()
  21. {
  22. return Index.GetHashCode() ^ EqualityComparer<T>.Default.GetHashCode(Value) << 2;
  23. }
  24. public bool Equals(CollectionAddEvent<T> other)
  25. {
  26. return Index.Equals(other.Index) && EqualityComparer<T>.Default.Equals(Value, other.Value);
  27. }
  28. }
  29. public struct CollectionRemoveEvent<T> : IEquatable<CollectionRemoveEvent<T>>
  30. {
  31. public int Index { get; private set; }
  32. public T Value { get; private set; }
  33. public CollectionRemoveEvent(int index, T value)
  34. : this()
  35. {
  36. Index = index;
  37. Value = value;
  38. }
  39. public override string ToString()
  40. {
  41. return string.Format("Index:{0} Value:{1}", Index, Value);
  42. }
  43. public override int GetHashCode()
  44. {
  45. return Index.GetHashCode() ^ EqualityComparer<T>.Default.GetHashCode(Value) << 2;
  46. }
  47. public bool Equals(CollectionRemoveEvent<T> other)
  48. {
  49. return Index.Equals(other.Index) && EqualityComparer<T>.Default.Equals(Value, other.Value);
  50. }
  51. }
  52. public struct CollectionMoveEvent<T> : IEquatable<CollectionMoveEvent<T>>
  53. {
  54. public int OldIndex { get; private set; }
  55. public int NewIndex { get; private set; }
  56. public T Value { get; private set; }
  57. public CollectionMoveEvent(int oldIndex, int newIndex, T value)
  58. : this()
  59. {
  60. OldIndex = oldIndex;
  61. NewIndex = newIndex;
  62. Value = value;
  63. }
  64. public override string ToString()
  65. {
  66. return string.Format("OldIndex:{0} NewIndex:{1} Value:{2}", OldIndex, NewIndex, Value);
  67. }
  68. public override int GetHashCode()
  69. {
  70. return OldIndex.GetHashCode() ^ NewIndex.GetHashCode() << 2 ^ EqualityComparer<T>.Default.GetHashCode(Value) >> 2;
  71. }
  72. public bool Equals(CollectionMoveEvent<T> other)
  73. {
  74. return OldIndex.Equals(other.OldIndex) && NewIndex.Equals(other.NewIndex) && EqualityComparer<T>.Default.Equals(Value, other.Value);
  75. }
  76. }
  77. public struct CollectionReplaceEvent<T> : IEquatable<CollectionReplaceEvent<T>>
  78. {
  79. public int Index { get; private set; }
  80. public T OldValue { get; private set; }
  81. public T NewValue { get; private set; }
  82. public CollectionReplaceEvent(int index, T oldValue, T newValue)
  83. : this()
  84. {
  85. Index = index;
  86. OldValue = oldValue;
  87. NewValue = newValue;
  88. }
  89. public override string ToString()
  90. {
  91. return string.Format("Index:{0} OldValue:{1} NewValue:{2}", Index, OldValue, NewValue);
  92. }
  93. public override int GetHashCode()
  94. {
  95. return Index.GetHashCode() ^ EqualityComparer<T>.Default.GetHashCode(OldValue) << 2 ^ EqualityComparer<T>.Default.GetHashCode(NewValue) >> 2;
  96. }
  97. public bool Equals(CollectionReplaceEvent<T> other)
  98. {
  99. return Index.Equals(other.Index)
  100. && EqualityComparer<T>.Default.Equals(OldValue, other.OldValue)
  101. && EqualityComparer<T>.Default.Equals(NewValue, other.NewValue);
  102. }
  103. }
  104. // IReadOnlyList<out T> is from .NET 4.5
  105. public interface IReadOnlyReactiveCollection<T> : IEnumerable<T>
  106. {
  107. int Count { get; }
  108. T this[int index] { get; }
  109. IObservable<CollectionAddEvent<T>> ObserveAdd();
  110. IObservable<int> ObserveCountChanged(bool notifyCurrentCount = false);
  111. IObservable<CollectionMoveEvent<T>> ObserveMove();
  112. IObservable<CollectionRemoveEvent<T>> ObserveRemove();
  113. IObservable<CollectionReplaceEvent<T>> ObserveReplace();
  114. IObservable<Unit> ObserveReset();
  115. }
  116. public interface IReactiveCollection<T> : IList<T>, IReadOnlyReactiveCollection<T>
  117. {
  118. new int Count { get; }
  119. new T this[int index] { get; set; }
  120. void Move(int oldIndex, int newIndex);
  121. }
  122. [Serializable]
  123. public class ReactiveCollection<T> : Collection<T>, IReactiveCollection<T>, IDisposable
  124. {
  125. [NonSerialized]
  126. bool isDisposed = false;
  127. public ReactiveCollection()
  128. {
  129. }
  130. public ReactiveCollection(IEnumerable<T> collection)
  131. {
  132. if (collection == null) throw new ArgumentNullException("collection");
  133. foreach (var item in collection)
  134. {
  135. Add(item);
  136. }
  137. }
  138. public ReactiveCollection(List<T> list)
  139. : base(list != null ? new List<T>(list) : null)
  140. {
  141. }
  142. protected override void ClearItems()
  143. {
  144. var beforeCount = Count;
  145. base.ClearItems();
  146. if (collectionReset != null) collectionReset.OnNext(Unit.Default);
  147. if (beforeCount > 0)
  148. {
  149. if (countChanged != null) countChanged.OnNext(Count);
  150. }
  151. }
  152. protected override void InsertItem(int index, T item)
  153. {
  154. base.InsertItem(index, item);
  155. if (collectionAdd != null) collectionAdd.OnNext(new CollectionAddEvent<T>(index, item));
  156. if (countChanged != null) countChanged.OnNext(Count);
  157. }
  158. public void Move(int oldIndex, int newIndex)
  159. {
  160. MoveItem(oldIndex, newIndex);
  161. }
  162. protected virtual void MoveItem(int oldIndex, int newIndex)
  163. {
  164. T item = this[oldIndex];
  165. base.RemoveItem(oldIndex);
  166. base.InsertItem(newIndex, item);
  167. if (collectionMove != null) collectionMove.OnNext(new CollectionMoveEvent<T>(oldIndex, newIndex, item));
  168. }
  169. protected override void RemoveItem(int index)
  170. {
  171. T item = this[index];
  172. base.RemoveItem(index);
  173. if (collectionRemove != null) collectionRemove.OnNext(new CollectionRemoveEvent<T>(index, item));
  174. if (countChanged != null) countChanged.OnNext(Count);
  175. }
  176. protected override void SetItem(int index, T item)
  177. {
  178. T oldItem = this[index];
  179. base.SetItem(index, item);
  180. if (collectionReplace != null) collectionReplace.OnNext(new CollectionReplaceEvent<T>(index, oldItem, item));
  181. }
  182. [NonSerialized]
  183. Subject<int> countChanged = null;
  184. public IObservable<int> ObserveCountChanged(bool notifyCurrentCount = false)
  185. {
  186. if (isDisposed) return Observable.Empty<int>();
  187. var subject = countChanged ?? (countChanged = new Subject<int>());
  188. if (notifyCurrentCount)
  189. {
  190. return subject.StartWith(() => this.Count);
  191. }
  192. else
  193. {
  194. return subject;
  195. }
  196. }
  197. [NonSerialized]
  198. Subject<Unit> collectionReset = null;
  199. public IObservable<Unit> ObserveReset()
  200. {
  201. if (isDisposed) return Observable.Empty<Unit>();
  202. return collectionReset ?? (collectionReset = new Subject<Unit>());
  203. }
  204. [NonSerialized]
  205. Subject<CollectionAddEvent<T>> collectionAdd = null;
  206. public IObservable<CollectionAddEvent<T>> ObserveAdd()
  207. {
  208. if (isDisposed) return Observable.Empty<CollectionAddEvent<T>>();
  209. return collectionAdd ?? (collectionAdd = new Subject<CollectionAddEvent<T>>());
  210. }
  211. [NonSerialized]
  212. Subject<CollectionMoveEvent<T>> collectionMove = null;
  213. public IObservable<CollectionMoveEvent<T>> ObserveMove()
  214. {
  215. if (isDisposed) return Observable.Empty<CollectionMoveEvent<T>>();
  216. return collectionMove ?? (collectionMove = new Subject<CollectionMoveEvent<T>>());
  217. }
  218. [NonSerialized]
  219. Subject<CollectionRemoveEvent<T>> collectionRemove = null;
  220. public IObservable<CollectionRemoveEvent<T>> ObserveRemove()
  221. {
  222. if (isDisposed) return Observable.Empty<CollectionRemoveEvent<T>>();
  223. return collectionRemove ?? (collectionRemove = new Subject<CollectionRemoveEvent<T>>());
  224. }
  225. [NonSerialized]
  226. Subject<CollectionReplaceEvent<T>> collectionReplace = null;
  227. public IObservable<CollectionReplaceEvent<T>> ObserveReplace()
  228. {
  229. if (isDisposed) return Observable.Empty<CollectionReplaceEvent<T>>();
  230. return collectionReplace ?? (collectionReplace = new Subject<CollectionReplaceEvent<T>>());
  231. }
  232. void DisposeSubject<TSubject>(ref Subject<TSubject> subject)
  233. {
  234. if (subject != null)
  235. {
  236. try
  237. {
  238. subject.OnCompleted();
  239. }
  240. finally
  241. {
  242. subject.Dispose();
  243. subject = null;
  244. }
  245. }
  246. }
  247. #region IDisposable Support
  248. private bool disposedValue = false;
  249. protected virtual void Dispose(bool disposing)
  250. {
  251. if (!disposedValue)
  252. {
  253. if (disposing)
  254. {
  255. DisposeSubject(ref collectionReset);
  256. DisposeSubject(ref collectionAdd);
  257. DisposeSubject(ref collectionMove);
  258. DisposeSubject(ref collectionRemove);
  259. DisposeSubject(ref collectionReplace);
  260. DisposeSubject(ref countChanged);
  261. }
  262. disposedValue = true;
  263. }
  264. }
  265. public void Dispose()
  266. {
  267. Dispose(true);
  268. }
  269. #endregion
  270. }
  271. public static partial class ReactiveCollectionExtensions
  272. {
  273. public static ReactiveCollection<T> ToReactiveCollection<T>(this IEnumerable<T> source)
  274. {
  275. return new ReactiveCollection<T>(source);
  276. }
  277. }
  278. }