Nel post precedente ho parlato di come utilizzare le classi di base del framework per realizzare una collection con le seguenti caratteristiche:
- all'inserimento di un nuovo elemento questo viene posizionato all'inizio della lista (i più recenti verranno visualizzati per primi)
- la lista deve contenere al massimo n elementi: l'inserimento di un nuovo elemento, raggiunto il limite n, deve prevedere la rimozione dell'elemento più vecchio presente nella lista (ovvero quello inserito da più tempo)
- garantire ottime performance
Il commento di rsaccav mi ha fatto convinto ad implementare una collezione generica "from scratch", così da colmare il vuoto lasciato in System.Collection.Generic.
Con estrema fantasia l'ho battezzata RollingList<T>
Il principio è piuttosto semplice: la lista si basa su un array con dimensione fissa pari al limite di elementi da conservare (Capacity; la proprietà deve essere impostata nel costruttore o comunque *prima* dell'inserimento del primo elemento) associato ad un cursore (backward) che definisce la posizione di inserimento/sostituzione e che consente di enumerare gli elementi nell'ordine corretto.
Il codice completo della classe:
using System;
using System.Collections;
using System.Threading;
public class RollingList<T> : ICollection
{
private int _cursor;
private int _size;
private int _capacity;
private T[] _items;
private object _syncRoot;
public RollingList(int capacity)
{
Capacity = capacity;
}
public int Capacity
{
get { return _capacity; }
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException("value", "Capacity must be greater than zero");
if (_capacity > 0)
throw new ArgumentException("Capacity has just been defined", "value");
_capacity = value;
_items = new T[_capacity];
_cursor = (_capacity - 1);
}
}
public void Add(T item)
{
if (_capacity <= 0)
throw new NotSupportedException("Capacity is not greater than zero");
_items[_cursor--] = item;
if (_size < _capacity)
_size++;
if (_cursor < 0)
_cursor = (_capacity - 1);
}
public void Clear()
{
Array.Clear(_items, 0, _size);
_size = 0;
_cursor = (_capacity - 1);
}
public T[] ToArray()
{
T[] destinationArray = new T[_size];
if (_size == 0)
return destinationArray;
int cursorPosition = (_cursor + 1 == _capacity) ? 0 : _cursor + 1;
Array.Copy(_items, cursorPosition, destinationArray, 0, (_capacity - cursorPosition));
if (_size == _capacity && cursorPosition > 0)
Array.Copy(_items, 0, destinationArray, (_capacity - cursorPosition), cursorPosition);
return destinationArray;
}
public void CopyTo(Array array, int index)
{
Array.Copy(ToArray(), 0, array, index, _size);
}
public int Count
{
get { return _size; }
}
public bool IsSynchronized
{
get { return false; }
}
public object SyncRoot
{
get
{
if (_syncRoot == null)
{
Interlocked.CompareExchange(ref _syncRoot, new object(), null);
}
return _syncRoot;
}
}
public IEnumerator GetEnumerator()
{
return ToArray().GetEnumerator();
}
}Questa implementazione è sicuramente migliorabile (ad esempio, oltre a ICollection potrebbe essere utile implementare l'interfaccia ICollection<T>, supportare la serializzazione, ecc.) ma è decisamente preferibile rispetto all'uso di Stack<T>, Queue<T> o LinkedList<T> in quanto:
- semplifica il codice di inserimento di elementi (le operazioni di IN/OUT al raggiungimento della capacità massima definita sono gestite dalla collection stessa, quindi non richiedono codice di controllo aggiuntivo)
- semplifica la lettura grazie ad una corretta enumerazione nativa degli elementi
- permette di avere performance notevolmente più elevate:
//
// RollingList performance test
//
int capacity = 10000;
int max = 1000000000;
DateTime start = DateTime.Now;
RollingList<int> list = new RollingList<int>(capacity);
for (int i = 1; i <= max; i++)
{
list.Add(i);
}
foreach (int item in list)
{
// do something with "item"
}
Console.WriteLine("RollingList: max={0} -> {1}",
max.ToString("#,###"),
DateTime.Now.Subtract(start)); Produce:
RollingList: max=1.000.000.000 -> 00:00:11.5610000
Ovvero un tempo di esecuzione che è circa un terzo rispetto a Queue<T>!!!
HTH
Come sempre sei troppo buono, grazie!
Spero possa essere utile a qualcuno (a me è servita eccome!) visto che, alla fine, è questo lo scopo del blog e, più in generale, della community.
Aspetto feedback
m.casati ha scritto:
Come sempre sei troppo buono, grazie!
Spero possa essere utile a qualcuno (a me è servita eccome!) visto che, alla fine, è questo lo scopo del blog e, più in generale, della community.
Mi servirà a breve
Ciao
m.casati [Staff] wrote:
Spero possa essere utile a qualcuno (a me è servita eccome!) visto che, alla fine, è questo lo scopo del blog e, più in generale, della community.
l'articolo è sempre lì ad aspettarti...
Tranquillo, ci sto lavorando, ma me la prendo comoda visto che le pubblicazioni sono sospese
Domanda: perchè non hai ereditato da Queue<T> ?, mi sembra che ti sarebbe bastato implementare un solo metodo per arrivare allo scopo.. mi sembra..
dops ha scritto:
perchè non hai ereditato da Queue<T> ?, mi sembra che ti sarebbe bastato implementare un solo metodo per arrivare allo scopo
A me non sembra, ma forse non ho capito cosa intendi. Puoi postare del codice per chiarire meglio?
vorrei provarci ma non ne ho il tempo, ripeto, è una impressione, ma forse mi sbaglio, quando mi servirà questo tipo di oggetto ci proverò
Aggiungi un nuovo commento »»»
Per inserire un commento, devi registrarti alla nostra community.




Stampa
Download
10annidi.ASPItalia.com: iscriviti alla competizione e vinci fantastici premi ogni mese!
BUG FIX
Ho modificato il metodo ToArray: la versione precedente generava un'eccezione se la lista non conteneva elementi.
HTH
Continua »»» | Rispondi »»»