Last active
May 7, 2019 19:46
-
-
Save praeclarum/2df15b98f4576c1815a35cb60790e4e6 to your computer and use it in GitHub Desktop.
This class is a new collection that is given a function to retrieve its own data. It uses ListDiff to minimize updates.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Specialized; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Threading; | |
using System.Linq; | |
using System.Diagnostics; | |
using System.Threading.Tasks; | |
namespace Praeclarum | |
{ | |
public class ObservableQuery<T> : IList<T>, IList, INotifyCollectionChanged | |
{ | |
public event NotifyCollectionChangedEventHandler CollectionChanged = delegate {}; | |
readonly Func<IEnumerable<T>> query; | |
// readonly ReaderWriterLockSlim itemsLock = new ReaderWriterLockSlim (); | |
SemaphoreSlim updateMutex = new SemaphoreSlim (1); | |
readonly List<T> items = new List<T> (); | |
Func<T, T, bool> match; | |
Timer throttleTimer; | |
const int ThrottleMillis = 100; | |
public ObservableQuery (Func<IEnumerable<T>> query, Func<T, T, bool> match = null) | |
{ | |
// Console.WriteLine ("OC Created on Thread: {0}", Thread.CurrentThread.ManagedThreadId); | |
this.query = query; | |
this.match = match ?? (Func<T, T, bool>)((a, b) => a.Equals (b)); | |
Refresh (); | |
} | |
public void Refresh () | |
{ | |
try { | |
throttleTimer = null; | |
LoadItems (); | |
} catch (Exception ex) { | |
Debug.WriteLine (ex); | |
if (!IgnoreRefreshExceptions) | |
throw; | |
} | |
} | |
public void ThrottledRefresh () | |
{ | |
if (throttleTimer == null) { | |
throttleTimer = new Timer ( | |
x => { | |
try { | |
Refresh (); | |
} catch (Exception ex) { | |
Log.Error (ex); | |
} | |
}, | |
null, | |
ThrottleMillis, | |
Timeout.Infinite); | |
} else { | |
throttleTimer.Change (ThrottleMillis, Timeout.Infinite); | |
} | |
} | |
void LoadItems () | |
{ | |
updateMutex.Wait (); | |
// Console.WriteLine ("OC LoadItems on Thread: {0}", Thread.CurrentThread.ManagedThreadId); | |
try { | |
var newItems = query ().ToList (); | |
var diff = new ListDiff<T,T> (items, newItems, match); | |
var adds = new List<T> (); | |
var removes = new List<T> (); | |
var p = 0; | |
var prevActionType = ListDiffActionType.Add; | |
for (var i = 0; i < diff.Actions.Count; i++) { | |
var a = diff.Actions [i]; | |
var actionTypeChanged = i != 0 && a.ActionType != prevActionType; | |
if (actionTypeChanged) { | |
EmitCollectionChanged (adds, removes, ref p); | |
} | |
switch (a.ActionType) { | |
case ListDiffActionType.Add: | |
adds.Add (a.DestinationItem); | |
break; | |
case ListDiffActionType.Remove: | |
removes.Add (a.SourceItem); | |
break; | |
case ListDiffActionType.Update: | |
p++; | |
break; | |
} | |
prevActionType = a.ActionType; | |
} | |
EmitCollectionChanged (adds, removes, ref p); | |
} finally { | |
updateMutex.Release (); | |
} | |
} | |
public bool IgnoreRefreshExceptions = false; | |
void EmitCollectionChanged (List<T> adds, List<T> removes, ref int p) | |
{ | |
if (adds.Count > 0) { | |
items.InsertRange (p, adds); | |
try { | |
CollectionChanged (this, new NotifyCollectionChangedEventArgs ( | |
NotifyCollectionChangedAction.Add, | |
adds, | |
p)); | |
} catch (Exception ex) { | |
Debug.WriteLine (ex); | |
if (!IgnoreRefreshExceptions) | |
throw; | |
} | |
p += adds.Count; | |
adds.Clear (); | |
} | |
if (removes.Count > 0) { | |
items.RemoveRange (p, removes.Count); | |
try { | |
CollectionChanged (this, new NotifyCollectionChangedEventArgs ( | |
NotifyCollectionChangedAction.Remove, | |
removes, | |
p)); | |
} catch (Exception ex) { | |
Debug.WriteLine (ex); | |
if (!IgnoreRefreshExceptions) | |
throw; | |
} | |
removes.Clear (); | |
} | |
} | |
#region IList implementation | |
public int IndexOf (T item) | |
{ | |
// itemsLock.EnterReadLock (); | |
// try { | |
return items.IndexOf (item); | |
// } finally { | |
// itemsLock.ExitReadLock (); | |
// } | |
} | |
public void Insert (int index, T item) | |
{ | |
throw new NotSupportedException (); | |
} | |
public void RemoveAt (int index) | |
{ | |
throw new NotSupportedException (); | |
} | |
public T this [int index] { | |
get { | |
// itemsLock.EnterReadLock (); | |
// try { | |
return items [index]; | |
// } finally { | |
// itemsLock.ExitReadLock (); | |
// } | |
} | |
// Analysis disable once ValueParameterNotUsed | |
set { | |
} | |
} | |
object IList.this [int index] { | |
get { | |
// itemsLock.EnterReadLock (); | |
// try { | |
return items [index]; | |
// } finally { | |
// itemsLock.ExitReadLock (); | |
// } | |
} | |
// Analysis disable once ValueParameterNotUsed | |
set { | |
} | |
} | |
int IList.Add (object value) | |
{ | |
throw new NotSupportedException (); | |
} | |
bool IList.Contains (object value) | |
{ | |
// itemsLock.EnterReadLock (); | |
// try { | |
return ((IList)items).Contains (value); | |
// } finally { | |
// itemsLock.ExitReadLock (); | |
// } | |
} | |
int IList.IndexOf (object value) | |
{ | |
// itemsLock.EnterReadLock (); | |
// try { | |
return ((IList)items).IndexOf (value); | |
// } finally { | |
// itemsLock.ExitReadLock (); | |
// } | |
} | |
void IList.Insert (int index, object value) | |
{ | |
throw new NotSupportedException (); | |
} | |
void IList.Remove (object value) | |
{ | |
throw new NotSupportedException (); | |
} | |
bool IList.IsFixedSize { | |
get { | |
return true; | |
} | |
} | |
#endregion | |
#region ICollection implementation | |
public void Add (T item) | |
{ | |
throw new NotSupportedException (); | |
} | |
public void Clear () | |
{ | |
throw new NotSupportedException (); | |
} | |
public bool Contains (T item) | |
{ | |
// itemsLock.EnterReadLock (); | |
// try { | |
return items.Contains (item); | |
// } finally { | |
// itemsLock.ExitReadLock (); | |
// } | |
} | |
public void CopyTo (T[] array, int arrayIndex) | |
{ | |
// itemsLock.EnterReadLock (); | |
// try { | |
items.CopyTo (array, arrayIndex); | |
// } finally { | |
// itemsLock.ExitReadLock (); | |
// } | |
} | |
public bool Remove (T item) | |
{ | |
throw new NotSupportedException (); | |
} | |
public int Count { | |
get { | |
// itemsLock.EnterReadLock (); | |
// try { | |
return items.Count; | |
// } finally { | |
// itemsLock.ExitReadLock (); | |
// } | |
} | |
} | |
public bool IsReadOnly { | |
get { | |
return true; | |
} | |
} | |
void ICollection.CopyTo (Array array, int index) | |
{ | |
// itemsLock.EnterReadLock (); | |
// try { | |
((ICollection)items).CopyTo (array, index); | |
// } finally { | |
// itemsLock.ExitReadLock (); | |
// } | |
} | |
bool ICollection.IsSynchronized { | |
get { | |
return false; | |
} | |
} | |
object syncRoot = null; | |
object ICollection.SyncRoot { | |
get { | |
if (syncRoot == null) | |
syncRoot = new object (); | |
return syncRoot; | |
} | |
} | |
#endregion | |
#region IEnumerable implementation | |
public IEnumerator<T> GetEnumerator () | |
{ | |
// itemsLock.EnterReadLock (); | |
// try { | |
return items.ToList ().GetEnumerator (); | |
// } finally { | |
// itemsLock.ExitReadLock (); | |
// } | |
} | |
IEnumerator IEnumerable.GetEnumerator () | |
{ | |
// itemsLock.EnterReadLock (); | |
// try { | |
return items.ToList ().GetEnumerator (); | |
// } finally { | |
// itemsLock.ExitReadLock (); | |
// } | |
} | |
#endregion | |
} | |
public class ObservableCollectionQuery<T> : ObservableQuery<T> | |
{ | |
readonly INotifyCollectionChanged notifier; | |
public ObservableCollectionQuery (object collection, Func<IEnumerable<T>> query, Func<T, T, bool> match = null) | |
: base (query, match) | |
{ | |
notifier = collection as INotifyCollectionChanged; | |
IsObserving = true; | |
} | |
bool monitor = false; | |
public bool IsObserving | |
{ | |
get { return monitor; } | |
set { | |
if (monitor == value) | |
return; | |
monitor = value; | |
if (notifier == null) | |
return; | |
if (monitor) { | |
notifier.CollectionChanged += HandleChange; | |
} else { | |
notifier.CollectionChanged -= HandleChange; | |
} | |
} | |
} | |
void HandleChange (object sender, NotifyCollectionChangedEventArgs e) | |
{ | |
ThrottledRefresh (); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Relies on ListDiff from https://gist.github.com/praeclarum/15d5dd96c27a9b6cd0bf15a2f222ce63