using System;
using System.Collections;
using System.Collections.Generic;
namespace TestProject
{
/// <summary>
/// 가상화 컬렉션
/// </summary>
/// <typeparam name="TItem">항목 타입</typeparam>
public class VirtualizingCollection<TItem> : IList<TItem>, IList
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 항목 공급자
/// </summary>
private readonly IItemProvider<TItem> itemProvider;
/// <summary>
/// 페이지 크기
/// </summary>
private readonly int pageSize = 100;
/// <summary>
/// 페이지 타임아웃
/// </summary>
private readonly long pageTimeout = 10000;
/// <summary>
/// 카운트
/// </summary>
private int count = -1;
/// <summary>
/// 페이지 딕셔너리
/// </summary>
private readonly Dictionary<int, IList<TItem>> pageDictionary = new Dictionary<int, IList<TItem>>();
/// <summary>
/// 페이지 터치 시간 딕셔너리
/// </summary>
private readonly Dictionary<int, DateTime> pageTouchTimeDictionary = new Dictionary<int, DateTime>();
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 항목 공급자 - ItemProvider
/// <summary>
/// 항목 공급자
/// </summary>
public IItemProvider<TItem> ItemProvider
{
get
{
return this.itemProvider;
}
}
#endregion
#region 페이지 크기 - PageSize
/// <summary>
/// 페이지 크기
/// </summary>
public int PageSize
{
get
{
return this.pageSize;
}
}
#endregion
#region 페이지 타임아웃 - PageTimeout
/// <summary>
/// 페이지 타임아웃
/// </summary>
public long PageTimeout
{
get
{
return this.pageTimeout;
}
}
#endregion
#region 카운트 - Count
/// <summary>
/// 카운트
/// </summary>
public virtual int Count
{
get
{
if(this.count == -1)
{
LoadCount();
}
return this.count;
}
protected set
{
this.count = value;
}
}
#endregion
#region 인덱서 - this[index]
/// <summary>
/// 인덱서
/// </summary>
/// <param name="index">인덱스</param>
/// <returns>항목</returns>
public TItem this[int index]
{
get
{
int pageIndex = index / PageSize;
int pageOffset = index % PageSize;
RequestPage(pageIndex);
if(pageOffset > PageSize / 2 && pageIndex < Count / PageSize)
{
RequestPage(pageIndex + 1);
}
if(pageOffset < PageSize / 2 && pageIndex > 0)
{
RequestPage(pageIndex - 1);
}
CleanUpPages();
if(this.pageDictionary[pageIndex] == null)
{
return default(TItem);
}
return this.pageDictionary[pageIndex][pageOffset];
}
set
{
throw new NotSupportedException();
}
}
#endregion
#region 동기 루트 - SyncRoot
/// <summary>
/// 동기 루트
/// </summary>
public object SyncRoot
{
get
{
return this;
}
}
#endregion
#region 동기화 여부 - IsSynchronized
/// <summary>
/// 동기화 여부
/// </summary>
public bool IsSynchronized
{
get
{
return false;
}
}
#endregion
#region 읽기 전용 여부 - IsReadOnly
/// <summary>
/// 읽기 전용 여부
/// </summary>
public bool IsReadOnly
{
get
{
return true;
}
}
#endregion
#region 고정 크기 여부 - IsFixedSize
/// <summary>
/// 고정 크기 여부
/// </summary>
public bool IsFixedSize
{
get
{
return false;
}
}
#endregion
#region (IList) 인덱서 - this[index]
/// <summary>
/// 인덱서
/// </summary>
/// <param name="index">인덱스</param>
/// <returns>항목</returns>
object IList.this[int index]
{
get
{
return this[index];
}
set
{
throw new NotSupportedException();
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - VirtualizingCollection(itemProvider)
/// <summary>
/// 생성자
/// </summary>
/// <param name="itemProvider">항목 공급자</param>
public VirtualizingCollection(IItemProvider<TItem> itemProvider)
{
this.itemProvider = itemProvider;
}
#endregion
#region 생성자 - VirtualizingCollection(itemProvider, pageSize)
/// <summary>
/// 생성자
/// </summary>
/// <param name="itemProvider">항목 공급자</param>
/// <param name="pageSize">페이지 크기</param>
public VirtualizingCollection(IItemProvider<TItem> itemProvider, int pageSize)
{
this.itemProvider = itemProvider;
this.pageSize = pageSize;
}
#endregion
#region 생성자 - VirtualizingCollection(itemProvider, pageSize, pageTimeout)
/// <summary>
/// 생성자
/// </summary>
/// <param name="itemProvider">항목 공급자</param>
/// <param name="pageSize">페이지 크기</param>
/// <param name="pageTimeout">페이지 타임아웃</param>
public VirtualizingCollection(IItemProvider<TItem> itemProvider, int pageSize, int pageTimeout)
{
this.itemProvider = itemProvider;
this.pageSize = pageSize;
this.pageTimeout = pageTimeout;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 페이지들 지우기 - CleanUpPages()
/// <summary>
/// 페이지들 지우기
/// </summary>
public void CleanUpPages()
{
List<int> keyList = new List<int>(this.pageTouchTimeDictionary.Keys);
foreach(int key in keyList)
{
if(key != 0 && (DateTime.Now - this.pageTouchTimeDictionary[key]).TotalMilliseconds > PageTimeout)
{
this.pageDictionary.Remove(key);
this.pageTouchTimeDictionary.Remove(key);
}
}
}
#endregion
#region 열거자 구하기 - GetEnumerator()
/// <summary>
/// 열거자 구하기
/// </summary>
/// <returns>열거자</returns>
public IEnumerator<TItem> GetEnumerator()
{
for(int i = 0; i < Count; i++)
{
yield return this[i];
}
}
#endregion
#region 포함 여부 구하기 - Contains(item)
/// <summary>
/// 포함 여부 구하기
/// </summary>
/// <param name="item">항목</param>
/// <returns>포함 여부</returns>
public bool Contains(TItem item)
{
return false;
}
#endregion
#region 추가하기 - Add(item)
/// <summary>
/// 추가하기
/// </summary>
/// <param name="item">항목</param>
public void Add(TItem item)
{
throw new NotSupportedException();
}
#endregion
#region 삽입하기 - Insert(index, item)
/// <summary>
/// 삽입하기
/// </summary>
/// <param name="index">인덱스</param>
/// <param name="item">항목</param>
public void Insert(int index, TItem item)
{
throw new NotSupportedException();
}
#endregion
#region 인덱스 구하기 - IndexOf(item)
/// <summary>
/// 인덱스 구하기
/// </summary>
/// <param name="item">항목</param>
/// <returns>인덱스</returns>
public int IndexOf(TItem item)
{
return -1;
}
#endregion
#region 제거하기 - RemoveAt(index)
/// <summary>
/// 제거하기
/// </summary>
/// <param name="index">인덱스</param>
public void RemoveAt(int index)
{
throw new NotSupportedException();
}
#endregion
#region 제거하기 - Remove(item)
/// <summary>
/// 제거하기
/// </summary>
/// <param name="item">항목</param>
/// <returns>처리 결과</returns>
public bool Remove(TItem item)
{
throw new NotSupportedException();
}
#endregion
#region 지우기 - Clear()
/// <summary>
/// 지우기
/// </summary>
public void Clear()
{
throw new NotSupportedException();
}
#endregion
#region 복사하기 - CopyTo(itemArray, arrayIndex)
/// <summary>
/// 복사하기
/// </summary>
/// <param name="itemArray">항목 배열</param>
/// <param name="arrayIndex">배열 인덱스</param>
public void CopyTo(TItem[] itemArray, int arrayIndex)
{
throw new NotSupportedException();
}
#endregion
#region (IEnumerable) 열거자 구하기 - GetEnumerator()
/// <summary>
/// 열거자 구하기
/// </summary>
/// <returns>열거자</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region (IList) 포함 여부 구하기 - Contains(value)
/// <summary>
/// 포함 여부 구하기
/// </summary>
/// <param name="value">값</param>
/// <returns>포함 여부</returns>
bool IList.Contains(object value)
{
return Contains((TItem)value);
}
#endregion
#region (IList) 추가하기 - Add(value)
/// <summary>
/// 추가하기
/// </summary>
/// <param name="value">값</param>
/// <returns>인덱스</returns>
int IList.Add(object value)
{
throw new NotSupportedException();
}
#endregion
#region (IList) 삽입하기 - Insert(index, value)
/// <summary>
/// 삽입하기
/// </summary>
/// <param name="index">인덱스</param>
/// <param name="value">값</param>
void IList.Insert(int index, object value)
{
Insert(index, (TItem)value);
}
#endregion
#region (IList) 인덱스 구하기 - IndexOf(value)
/// <summary>
/// 인덱스 구하기
/// </summary>
/// <param name="value">값</param>
/// <returns>인덱스</returns>
int IList.IndexOf(object value)
{
return IndexOf((TItem) value);
}
#endregion
#region (IList) 제거하기 - Remove(value)
/// <summary>
/// 제거하기
/// </summary>
/// <param name="value">값</param>
void IList.Remove(object value)
{
throw new NotSupportedException();
}
#endregion
#region (ICollection) 복사하기 - CopyTo(array, index)
/// <summary>
/// 복사하기
/// </summary>
/// <param name="array">배열</param>
/// <param name="index">인덱스</param>
void ICollection.CopyTo(Array array, int index)
{
throw new NotSupportedException();
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region 카운트 가져오기 - FetchCount()
/// <summary>
/// 카운트 가져오기
/// </summary>
/// <returns>카운트</returns>
protected int FetchCount()
{
return ItemProvider.FetchCount();
}
#endregion
#region 카운트 로드하기 - LoadCount()
/// <summary>
/// 카운트 로드하기
/// </summary>
protected virtual void LoadCount()
{
Count = FetchCount();
}
#endregion
#region 페이지 채우기 - PopulatePage(pageIndex, itemList)
/// <summary>
/// 페이지 채우기
/// </summary>
/// <param name="pageIndex">페이지 인덱스</param>
/// <param name="itemList">항목 리스트</param>
protected virtual void PopulatePage(int pageIndex, IList<TItem> itemList)
{
if(this.pageDictionary.ContainsKey(pageIndex))
{
this.pageDictionary[pageIndex] = itemList;
}
}
#endregion
#region 페이지 가져오기 - FetchPage(pageIndex)
/// <summary>
/// 페이지 가져오기
/// </summary>
/// <param name="pageIndex">페이지 인덱스</param>
/// <returns>항목 리스트</returns>
protected IList<TItem> FetchPage(int pageIndex)
{
return ItemProvider.FetchRange(pageIndex * PageSize, PageSize);
}
#endregion
#region 페이지 로드하기 - LoadPage(pageIndex)
/// <summary>
/// 페이지 로드하기
/// </summary>
/// <param name="pageIndex">페이지 인덱스</param>
protected virtual void LoadPage(int pageIndex)
{
PopulatePage(pageIndex, FetchPage(pageIndex));
}
#endregion
#region 페이지 요청하기 - RequestPage(pageIndex)
/// <summary>
/// 페이지 요청하기
/// </summary>
/// <param name="pageIndex">페이지 인덱스</param>
protected virtual void RequestPage(int pageIndex)
{
if(!this.pageDictionary.ContainsKey(pageIndex))
{
this.pageDictionary.Add(pageIndex, null);
this.pageTouchTimeDictionary.Add(pageIndex, DateTime.Now);
LoadPage(pageIndex);
}
else
{
this.pageTouchTimeDictionary[pageIndex] = DateTime.Now;
}
}
#endregion
}
}