W
Weeble
I was implementing a collection type today, and when I ran the tests
on it I was surprised to see a stack overflow. It turns out this was
the culprit:
public class MyCollection : ICollection<T>, IEnumerable<T>
{
[...]
public void CopyTo(T[] array, int arrayIndex)
{
List<T> list = new List<T>(this);
list.CopyTo(array, arrayIndex);
}
}
List<T> has three constructors: one with no arguments, one that takes
an integer, and one that takes an IEnumerable<T>. I had assumed that
the one that takes an IEnumerable<T> would create a new, empty list,
and iterate over my collection using the IEnumerable<T> interface,
adding items to the list. Instead, it appears to cast to
ICollection<T>, inspect Count, create itself at an appropriate size,
then call - uh oh - CopyTo in order to copy elements of my collection
straight into itself. D'oh! I'd better write CopyTo the long way...
As a related note, this data structure doesn't have an O(1)
implementation of Count. (I don't think it's actually possible to do
that and meet some of its other complexity requirements.) Is it wise
to implement ICollection<T> at all? I'm a bit worried that algorithms
assume that Count is cheap and will call it unexpectedly. I'm
*especially* worried that this can happen even in methods that take
IEnumerable<T> and not ICollection<T>, given my nasty surprise with
List's constructor. How useful is it to implement ICollection<T>
anyway?
on it I was surprised to see a stack overflow. It turns out this was
the culprit:
public class MyCollection : ICollection<T>, IEnumerable<T>
{
[...]
public void CopyTo(T[] array, int arrayIndex)
{
List<T> list = new List<T>(this);
list.CopyTo(array, arrayIndex);
}
}
List<T> has three constructors: one with no arguments, one that takes
an integer, and one that takes an IEnumerable<T>. I had assumed that
the one that takes an IEnumerable<T> would create a new, empty list,
and iterate over my collection using the IEnumerable<T> interface,
adding items to the list. Instead, it appears to cast to
ICollection<T>, inspect Count, create itself at an appropriate size,
then call - uh oh - CopyTo in order to copy elements of my collection
straight into itself. D'oh! I'd better write CopyTo the long way...
As a related note, this data structure doesn't have an O(1)
implementation of Count. (I don't think it's actually possible to do
that and meet some of its other complexity requirements.) Is it wise
to implement ICollection<T> at all? I'm a bit worried that algorithms
assume that Count is cheap and will call it unexpectedly. I'm
*especially* worried that this can happen even in methods that take
IEnumerable<T> and not ICollection<T>, given my nasty surprise with
List's constructor. How useful is it to implement ICollection<T>
anyway?