using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Developpez.Dotnet.Properties;
namespace Developpez.Dotnet.Collections
{
///
/// Classe de base pour des collections réalisant la virtualisation des données. L'implémentation de base est en lecture seule.
///
/// Type des éléments de la collection
/// Les données sont chargées page par page au fur et à mesure des besoins. Seules les pages adjacentes à la dernière page accédée sont conservées en mémoire.
public abstract class VirtualizingCollectionBase : IList, IList, INotifyPropertyChanged
{
#region Private data
private readonly object _syncRoot;
private readonly int _pageSize;
private readonly Dictionary _pages;
private int? _count;
#endregion
#region Constructor
///
/// Initialise une nouvelle instance de VirtualizingCollectionBase
///
/// Nombre d'éléments par page de données
protected VirtualizingCollectionBase(int pageSize)
{
pageSize.CheckArgumentOutOfRange("pageSize", 0, int.MaxValue);
_syncRoot = new object();
_pageSize = pageSize;
_pages = new Dictionary();
}
#endregion
#region Implementation of IEnumerable
///
/// Renvoie un énumérateur pour parcourir les éléments de la collection
///
/// Un énumérateur pour parcourir les éléments de la collection
public IEnumerator GetEnumerator()
{
for (int i = 0; i < Count; i++)
{
yield return this[i];
}
}
///
/// Renvoie un énumérateur pour parcourir les éléments de la collection
///
/// Un énumérateur pour parcourir les éléments de la collection
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region Implementation of ICollection
///
/// Non supporté. Ajoute l'élément spécifié à la collection.
///
/// Élément à ajouter
/// La collection est en lecture seule.
public virtual void Add(T item)
{
throw ReadOnlyException();
}
///
/// Non supporté. Vide la collection.
///
/// La collection est en lecture seule.
public virtual void Clear()
{
throw ReadOnlyException();
}
///
/// Indique si la collection contient l'élément spécifié
///
/// Élément dont on veut tester la présence
/// true si l'élément est présent dans la collection, sinon false
/// Renvoie toujours false dans l'implémentation de base.
public virtual bool Contains(T item)
{
return false;
}
///
/// Copie les éléments de la collection vers le tableau spécifié
///
/// Tableau vers lequel les éléments sont copiés
/// Position dans le tableau à laquelle la copie commence
public virtual void CopyTo(T[] array, int arrayIndex)
{
array.CheckArgumentNull("array");
arrayIndex.CheckArgumentOutOfRange("arrayIndex", 0, int.MaxValue);
if (array.Length < Count + arrayIndex)
throw new ArgumentException(ExceptionMessages.ArrayCapacityInsufficient);
for (int i = 0; i < Count; i++)
{
array[arrayIndex + i] = this[i];
}
}
///
/// Non supporté. Retire l'élément spécifié de la collection.
///
/// Élément à retirer.
/// true si l'élément était présent et a été supprimé, sinon false
/// La collection est en lecture seule.
public virtual bool Remove(T item)
{
throw ReadOnlyException();
}
///
/// Renvoie le nombre d'éléments dans la collection
///
public int Count
{
get
{
if (_count == null)
_count = FetchCount();
return _count.Value;
}
}
///
/// Indique si la collection est en lecture seule. Renvoie toujours true dans l'implémentation de base.
///
public virtual bool IsReadOnly
{
get { return true; }
}
#endregion
#region Implementation of IList
///
/// Renvoie la position de l'élément spécifié dans la collection.
///
/// Élément recherché
/// La position de l'élément dans la collection, ou -1 si l'élément n'est pas dans la collection
/// Renvoie toujours -1 dans l'implémentation de base.
public virtual int IndexOf(T item)
{
return -1;
}
///
/// Non supporté. Insère l'élément spécifié dans la collection à la position spécifiée.
///
/// Position à laquelle insérer l'élément
/// Élément à insérer
/// La collection est en lecture seule.
public virtual void Insert(int index, T item)
{
throw ReadOnlyException();
}
///
/// Non supporté. Retire l'élément à la position spécifiée
///
/// Position de l'élément à retirer
/// La collection est en lecture seule.
public virtual void RemoveAt(int index)
{
throw ReadOnlyException();
}
///
/// Obtient l'élément à la position spécifiée. L'accesseur set n'est pas supporté.
///
/// Position de l'élément à obtenir
/// L'élément à la position spécifiée
/// La propriété est affectée et la collection est en lecture seule.
public virtual T this[int index]
{
get
{
index.CheckArgumentOutOfRange("index", 0, Count - 1);
int indexInPage;
int pageIndex = Math.DivRem(index, PageSize, out indexInPage);
T[] page = GetPage(pageIndex);
return page[indexInPage];
}
set { throw ReadOnlyException(); }
}
#endregion
#region Explicit implementation of ICollection
///
/// Copie les éléments de la collection vers le tableau spécifié
///
/// Tableau vers lequel les éléments sont copiés
/// Position dans le tableau à laquelle la copie commence
void ICollection.CopyTo(Array array, int index)
{
CopyTo((T[])array, index);
}
///
/// Obtient un objet qui peut être utilisé pour synchroniser l'accès à la collection.
///
object ICollection.SyncRoot
{
get { return _syncRoot; }
}
///
/// Obtient une valeur indiquant si l'accès à la collection est synchronisé
///
bool ICollection.IsSynchronized
{
get { return false; }
}
#endregion
#region Explicit implementation of IList
///
/// Non supporté. Ajoute l'élément spécifié à la collection.
///
/// Élément à ajouter
/// La position à laquelle l'élément a été ajouté
/// La collection est en lecture seule.
int IList.Add(object value)
{
Add((T)value);
return Count;
}
///
/// Indique si la collection contient l'élément spécifié
///
/// Élément dont on veut tester la présence
/// true si l'élément est présent dans la collection, sinon false
/// Renvoie toujours false dans l'implémentation de base.
bool IList.Contains(object value)
{
return Contains((T)value);
}
///
/// Non supporté. Insère l'élément spécifié dans la collection à la position spécifiée.
///
/// Position à laquelle insérer l'élément
/// Élément à insérer
/// La collection est en lecture seule.
void IList.Insert(int index, object value)
{
Insert(index, (T)value);
}
///
/// Non supporté. Retire l'élément spécifié de la collection.
///
/// Élément à retirer.
/// true si l'élément était présent et a été supprimé, sinon false
/// La collection est en lecture seule.
void IList.Remove(object value)
{
Remove((T)value);
}
///
/// Renvoie la position de l'élément spécifié dans la collection.
///
/// Élément recherché
/// La position de l'élément dans la collection, ou -1 si l'élément n'est pas dans la collection
/// Renvoie toujours -1 dans l'implémentation de base.
int IList.IndexOf(object value)
{
return IndexOf((T)value);
}
///
/// Obtient l'élément à la position spécifiée. L'accesseur set n'est pas supporté.
///
/// Position de l'élément à obtenir
/// L'élément à la position spécifiée
/// La propriété est affectée et la collection est en lecture seule.
object IList.this[int index]
{
get { return this[index]; }
set { this[index] = (T)value; }
}
///
/// Indique si la collection est de taille fixe. Renvoie toujours true dans l'implémentation de base.
///
bool IList.IsFixedSize
{
get { return IsReadOnly; }
}
#endregion
#region Implementation of INotifyPropertyChanged
///
/// Se produit quand une propriété de la collection a changé
///
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Abstract methods
///
/// Quand cette méthode est implémentée dans une classe dérivée, obtient les éléments de la plage spécifiée.
///
/// Position du premier élément à obtenir
/// Nombre d'éléments à obtenir
/// Un tableau contenant les éléments demandés
/// Les classes dérivées doivent implémenter cette méthode pour récupérer les éléments de la collection à partir d'une source quelconque.
protected abstract T[] FetchItems(int start, int count);
///
/// Quand cette méthode est implémentée dans une classe dérivée, obtient le nombre total d'éléments de la collection.
///
/// Nombre total d'éléments de la collection
/// Les classes dérivées doivent implémenter cette méthode pour récupérer le nombre d'éléments de la collection à partir d'une source quelconque.
protected abstract int FetchCount();
#endregion
#region Public members
///
/// Invalide les pages en cache dans la collection et le nombre d'éléments.
/// Le prochain accès à un élément rechargera la page nécessaire, et le nombre d'éléments sera réévalué.
///
public virtual void Invalidate()
{
_count = null;
_pages.Clear();
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(string.Empty));
}
///
/// Obtient le nombre d'éléments par page de données.
///
public int PageSize
{
get { return _pageSize; }
}
#endregion
#region Private implementation
private static Exception ReadOnlyException()
{
throw new NotSupportedException(ExceptionMessages.CollectionIsReadOnly);
}
private T[] GetPage(int pageIndex)
{
T[] page;
if (!_pages.TryGetValue(pageIndex, out page))
{
int start = pageIndex * PageSize;
page = FetchItems(start, PageSize);
_pages[pageIndex] = page;
RemoveOldPages(pageIndex);
}
return page;
}
private void RemoveOldPages(int lastLoadedPageIndex)
{
var indexesToRemove =
_pages.Keys
.Where(i => i < lastLoadedPageIndex - 1 || i > lastLoadedPageIndex + 1)
.ToArray();
foreach (var index in indexesToRemove)
{
_pages.Remove(index);
}
}
#endregion
}
}