Feeling lazy part one
As part of a project I've been working on recently I needed to bind a potentially quite large collection to a listbox, due to the unknown size of the collection I didn't want to load it into memory until I actually had to. To get around this problem I decided to create a new lazy observable collection class that would leverage the power of IQueryable to delay loading of the collection until it's actually required.
The beauty of this class is that you can instantiate it by passing an IQueryable in to the constructor and then forget about it and it won't load anything until you start working with the collection at which point it gets populated by the relevant query.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
namespace Lazy
{
/// <summary>
/// represents a LazyCollection class that uses IQueryable for delayed loading and implements ICollection, INotifyCollectionChanged and INotifyPropertyChanged
/// </summary>
/// <typeparam name="T">the object type T</typeparam>
public class LazyCollection<T> : ICollection<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
#region members
private IList<T> _list;
private readonly IQueryable<T> _query;
private readonly Monitor _monitor;
#endregion
#region properties
/// <summary>
/// gets whether the collection has been loaded
/// </summary>
public bool HasLoaded
{
get { return _list != null; }
}
/// <summary>
/// gets the items
/// </summary>
protected IList<T> Items
{
get
{
if (_list == null)
{
_list = _query.ToList();
}
return _list;
}
}
/// <summary>
/// gets/sets the item from the list at the specified index
/// </summary>
/// <param name="index">the index of the item in the list</param>
/// <returns>the item</returns>
public T this[int index]
{
get
{
return Items[index];
}
set
{
Items[index] = value;
OnCollectionChanged(NotifyCollectionChangedAction.Replace, Items[index], Count);
}
}
#endregion
#region constructors
/// <summary>
/// default constructor
/// </summary>
public LazyCollection()
{
//set properties
_list = new List<T>();
_monitor = new Monitor();
}
/// <summary>
/// default constructor
/// </summary>
/// <param name="query">an IQueryable of type T</param>
public LazyCollection(IQueryable<T> query)
{
//set properties
_query = query;
_monitor = new Monitor();
}
#endregion
#region methods
/// <summary>
/// prevents reentrant attempts to changes to the collection
/// </summary>
/// <remarks>
/// reentrant attempts to change the collection can occure when a subscriber of an event attempts to make changes to the collection
/// </remarks>
protected IDisposable BlockReentrancy()
{
_monitor.Enter();
return _monitor;
}
/// <summary>
/// check and assert for reentrant attempts to change this collection.
/// </summary>
/// <remarks>
/// raises an InvalidOperationException when changing the collection whilst another change is being broadcast
/// </remarks>
protected void CheckReentrancy()
{
if ((_monitor.Busy && (CollectionChanged != null)) && (CollectionChanged.GetInvocationList().Length > 1))
{
throw new InvalidOperationException("Collection reentrancy Not Allowed");
}
}
#endregion
#region ICollection<T> Members
/// <summary>
/// adds the item to the collection
/// </summary>
/// <param name="item">the item to add</param>
public void Add(T item)
{
CheckReentrancy();
Items.Add(item);
OnPropertyChanged("Count");
OnCollectionChanged(NotifyCollectionChangedAction.Add, item, Count - 1);
}
/// <summary>
/// clears the collection
/// </summary>
public void Clear()
{
CheckReentrancy();
Items.Clear();
OnPropertyChanged("Count");
OnCollectionReset();
}
/// <summary>
/// determines whether the item is contained within the collection
/// </summary>
/// <param name="item">the item to find</param>
/// <returns>a boolean value indicating whether the item was found</returns>
public bool Contains(T item)
{
return Items.Contains(item);
}
/// <summary>
/// copies the collection to an array starting at the specified index
/// </summary>
/// <param name="array">the target array</param>
/// <param name="arrayIndex">the starting index</param>
public void CopyTo(T[] array, int arrayIndex)
{
Items.CopyTo(array, arrayIndex);
}
/// <summary>
/// gets the number of items in the collection
/// </summary>
public int Count
{
get { return Items.Count; }
}
/// <summary>
/// determines whether the collection is read only
/// </summary>
public bool IsReadOnly
{
get { return false; }
}
/// <summary>
/// removes the item from the collection
/// </summary>
/// <param name="item">the item to remove</param>
/// <returns>a boolean value indicating whether the item was removed</returns>
public bool Remove(T item)
{
CheckReentrancy();
var result = Items.Remove(item);
if (result)
{
OnPropertyChanged("Count");
OnCollectionChanged(NotifyCollectionChangedAction.Remove, item);
}
return result;
}
#endregion
#region IEnumerable<T> Members
/// <summary>
/// gets the enumerator to iterate over the collection
/// </summary>
/// <returns>IEnumerator</returns>
public IEnumerator<T> GetEnumerator()
{
return Items.GetEnumerator();
}
#endregion
#region IEnumerable Members
/// <summary>
/// gets the enumerator to iterate over the collection
/// </summary>
/// <returns>IEnumerator</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return Items.GetEnumerator();
}
#endregion
#region INotifyCollectionChanged Members
/// <summary>
/// CollectionChanged event
/// </summary>
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <summary>
/// raises the CollectionChanged event
/// </summary>
/// <param name="e">NotifyCollectionChangedEventArgs</param>
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (CollectionChanged != null)
{
using (BlockReentrancy())
{
CollectionChanged(this, e);
}
}
}
/// <summary>
/// raises the CollectionCHangedEvent
/// </summary>
/// <param name="action">the NotifyCollectionChangedAction</param>
/// <param name="item">the item(s) that caused the change event</param>
private void OnCollectionChanged(NotifyCollectionChangedAction action, object item)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, Items.Count));
}
/// <summary>
/// raises the CollectionChangedEvent
/// </summary>
/// <param name="action">the NotifyCollectionChangedAction</param>
/// <param name="item">the item that caused the change event</param>
/// <param name="index">the index of the item</param>
private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index));
}
/// <summary>
/// raises the CollectionChangedEvent
/// </summary>
private void OnCollectionReset()
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
#endregion
#region INotifyPropertyChanged Members
/// <summary>
/// PropertyChanged event
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// raises the PropertyChanged event
/// </summary>
/// <param name="property">the property name</param>
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
#endregion
#region Monitor
/// <summary>
/// represents a Monitor class that implements IDisposable
/// </summary>
/// <remarks>used to prevent reentrant calls</remarks>
private class Monitor : IDisposable
{
#region members
private int _busyCount;
#endregion
#region properties
/// <summary>
/// gets the busy state
/// </summary>
public bool Busy
{
get { return _busyCount > 0; }
}
#endregion
#region constructors
/// <summary>
/// default constructor
/// </summary>
public Monitor()
{
}
#endregion
#region methods
/// <summary>
/// increments the busy count
/// </summary>
public void Enter()
{
++_busyCount;
}
#endregion
#region IDisposable Members
/// <summary>
/// decrements the busy count
/// </summary>
public void Dispose()
{
--_busyCount;
}
#endregion
}
#endregion
}
}

0 Comments