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 } }