Zamiast tak odpisywać w komentarzach postanowiłem dodać swój wpis i kawałek kodu.
Podczas pisania założyłem że:
- index nie może być mniejszy niż 0
- pierwszy element wskazuje ma index 0
- skoro ma być to tablica rzadka wiec iterowanie powinno również zwracać domyślne wartości dla niezdefiniowanych elementów
- iterowanie jest wykonywane do elementu o najwyższym indeksie
- można zdefiniować jaka jest domyślna wartość, jeżeli nie brana jest domyślna dla typu elementów kolekcji
Zastosowanie IEnumerable<T> ma na celu ujednolicenie sposobu poruszania się po kolekcji (element po elemencie, od początku do końca) co wykorzystano w klasach LINQ.
Kopiuj
public class GenericArray<T>
: IEnumerable<T>
{
public GenericArray()
: this(default(T))
{
}
public GenericArray(T defaultValue)
{
_defaultValue = defaultValue;
}
private readonly T _defaultValue;
private readonly IDictionary<int, T> _items = new Dictionary<int, T>();
private int _itemsCount;
public T this[int index]
{
get
{
return _items.ContainsKey(index)
? _items[index]
: _defaultValue;
}
set
{
if (index <= 0)
{
throw new IndexOutOfRangeException();
}
if (!EqualityComparer<T>.Default.Equals(_defaultValue, value))
{
_items[index] = value;
return;
}
if (_items.ContainsKey(index))
{
_items.Remove(index);
}
}
}
public IEnumerator<T> GetEnumerator()
{
var index = 0;
foreach (var item in _items.OrderBy(k => k.Key))
{
while (index < item.Key)
{
index++;
yield return _defaultValue;
}
index++;
yield return item.Value;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Wykorzystałem tu kilka elementów .NET
!EqualityComparer<T>.Default
w ten sposób nie interesuje mnie jak porównywane są elementy czy typ T.
OrderBy
to metoda LINQ do sortowania. W końcu Dictionary to tez kolekcja a każda kolekcja jest IEnumerable<jakiś typ>, to jakiś typ to KeyValuePair<TKey, TValue> ... jeszcze więcej generyków
yield return
ułatwia zwracanie elementów kolekcji (gdy metoda ma zwracać IEnumerable<jakis typ="typ">). Kompilator generuje dla nas kod enumeratora
Poniżej chciałem pokazać kilka przykładów użycia LINQ (.NET 4.0). Żeby nie było ściemy do operacji na kolekcji używam osobnej zmiennej tylko typu IEnumerable<int>:
var instance = new GenericArray<int>(); // domyślna wartośc pusta
Kopiuj
var instance = new GenericArray<int>();
var instanceEnumerable = (IEnumerable<int>)instance;
AssertThat(instanceEnumerable.Count() == 0);
AssertThat(!instanceEnumerable.Any());
instance[9] = 10;
AssertThat(instanceEnumerable.Count() == 10);
AssertThat(instanceEnumerable.Any());
AssertThat(instance[9] == 10);
AssertThat(instance[4] == 0);
AssertThat(instanceEnumerable.ElementAt(9) == 10);
AssertThat(instanceEnumerable.Skip(9).First() == 10);
AssertThat(instanceEnumerable.ElementAt(4) == 0);
AssertThat(instanceEnumerable.Skip(4).First() == 0);
AssertThat(instanceEnumerable.Contains(10));
AssertThat(instanceEnumerable.Last() == 10);
instance[4] = 5;
AssertThat(instanceEnumerable.Count() == 10);
var list = instance.ToList();
AssertThat(list.Count == 10);
AssertThat(list[9] == 10);
AssertThat(list[4] == 5);
instance[9] = 0;
AssertThat(instanceEnumerable.Count() == 5);
AssertThat(instanceEnumerable.Any());
AssertThat(instance[4] == 5);
instance[4] = 0;
AssertThat(instanceEnumerable.Count() == 0);
AssertThat(!instanceEnumerable.Any());
gdzie meteoda AsssertThat to taki prosty silnik testujący aby nie wprowadzać żadnego farmeworka i nie gmatwać przykładu:
Kopiuj
public void AssertThat(bool isValid)
{
if (!isValid)
{
const string message = "Assertion failed.";
Console.WriteLine(message);
throw new Exception(message);
}
}
Implementacja ma pewne luki, ale one wynikają z założeń. Np. taka operacja przejdzie nawet na nowo stworzonej instancji.
Kopiuj
AssertThat(instance[999] == 0);
ale
Kopiuj
instance.Count() = 0);
No tak Count() (metoda LINQ nie ta z ICollection) zwraca nam 0 a tu mamy 1000 element. Tak działa przedstawiona implementacja. Nie ma definicji rozmiaru tablicy a Count() przechodzi po wszystkich elementach i je zlicza.
No własnie... IEnumerable<T> pozwala nam przechodzić po kolekcji elementów niezależnie od implementacji... ale tylko tyle nie pozwala nam zarządzać ta kolekcją. Do tego jest służy interfejs ICollection<T> pozwalając nam Dodawać, Usuwać, podmieniać czy dowiedzieć się ile mamy elementów. O ile chcielibyśmy aby nasza klasa mogła zastąpić na przykład klasę List<T> wtedy powinniśmy się nad zastanowić nad jej implementacją. Wspominając o tym interfejsie bardziej chciałem wskazać dalszy kierunek rozwoju implementacji a przy okazji rozważań o zachowaniu implementacji (np to zliczanie). "Dobra praktyka" mówi tylko o tym że każdy typ reprezentujący kolekcję powinien implementować IEnumerable<T>.
Mistzzz'u, dobrze ze piszesz kod pokazujący testowanie, to też "dobra praktyka". Może zainteresuje Cię nUnit lub xUnit albo MSTest. To jak je napisałem to takie małe demo aby je nieco zautomatyzować automatyzować ;).