Custom collection implementing IEnumerable<T>

G

Guest

Hi,

I have a custom collection that is basically a wrapper of an List<T>
collection. I want to expose the IEnumerable<T> interface of the collection
to the world, so on my custom collection, I implement both
IEnumerable/IEnumerator, and IEnumerable<T>/IEnumerator<T>. My code for
those interfaces just calls off to the internal collections's methods, such as

IEnumerator IEnumerable.GetEnumerator()
{
return myinternalList.GetEnumerator();
}

I am trying to bind my custom list to a control that can bind to a
collection that implements IEnumerable and/or IList (I expose the IList
interface for the internal collection in a similar way).

The problem is, everytime I execute something like

myControl.DataSource = myCustomCollection

I get a NullReferenceException. I can't really tell where it's coming from,
or what is null. Neither my custom collection nor the internal IList
collection are null.

Any ideas?

Jeff
 
J

Jon Skeet [C# MVP]

Jeff said:
I have a custom collection that is basically a wrapper of an List<T>
collection. I want to expose the IEnumerable<T> interface of the collection
to the world, so on my custom collection, I implement both
IEnumerable/IEnumerator, and IEnumerable<T>/IEnumerator<T>. My code for
those interfaces just calls off to the internal collections's methods, such as

IEnumerator IEnumerable.GetEnumerator()
{
return myinternalList.GetEnumerator();
}

I am trying to bind my custom list to a control that can bind to a
collection that implements IEnumerable and/or IList (I expose the IList
interface for the internal collection in a similar way).

The problem is, everytime I execute something like

myControl.DataSource = myCustomCollection

I get a NullReferenceException. I can't really tell where it's coming from,
or what is null. Neither my custom collection nor the internal IList
collection are null.

Any ideas?

Is myControl null by any chance? Have you tried it in a debugger and
seen whether GetEnumerator is being called at all?

What does the stack trace say?
 
G

Guest

myControl is not null, either.

GetEnumerator() is being called when the collection is bound to the control,
like you would expect. After GetEnumerator() is called, I noticed that the
Current property is null, so I thought that might be the problem, but I
called MoveFirst() before returning the enumerator and that didn't have any
effect either.

The stack trace doesn't show much, other than the control calling a few
internal binding routines. I am not able to debug the code where the
nullreferenceexception is actually being thrown.
 
J

Jon Skeet [C# MVP]

Jeff said:
myControl is not null, either.

GetEnumerator() is being called when the collection is bound to the control,
like you would expect. After GetEnumerator() is called, I noticed that the
Current property is null, so I thought that might be the problem, but I
called MoveFirst() before returning the enumerator and that didn't have any
effect either.

The stack trace doesn't show much, other than the control calling a few
internal binding routines. I am not able to debug the code where the
nullreferenceexception is actually being thrown.

Could you post a short but complete program which demonstrates the
problem?

See http://www.pobox.com/~skeet/csharp/complete.html for details of
what I mean by that.
 
G

Guest

Sure. Unfortunately I cannot view your link due to firewall restrictions
where I work, but I have since fixed the exception, and now when I bind to
the control (DataGridView in this case), the grid is not populated. Anyway,
here is the basic outline of the code:

This is my custom collection:


public class ObjectCollection<T> : System.Collections.Generic.IList<T>,
System.Collections.Generic.IEnumerable<T>,
System.Collections.Generic.IEnumerator<T>
{

List<T> _objectArray = new List<T>;

IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return _objectArray.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return _objectArray.GetEnumerator();
}

T System.Collections.Generic.IEnumerator<T>.Current
{
get { return _objectArray.GetEnumerator().Current; }
}

object IEnumerator.Current
{
get { return _objectArray.GetEnumerator().Current; }
}

bool IEnumerator.MoveNext()
{
return _objectArray.GetEnumerator().MoveNext();
}

void IEnumerator.Reset()
{
throw new Exception("The method or operation is not implemented.");
}

public int IndexOf(T item)
{
return _objectArray.IndexOf(item);
}

public void Insert(int index, T item)
{
_objectArray.Insert(index, item);
}

public void RemoveAt(int index)
{
_objectArray.RemoveAt(index);
}

public T this[int index]
{
get
{
return _objectArray[index];
}
set
{
_objectArray[index] = value;
}
}

public void Add(T newItem)
{
_objectArray.Add(newItem);
}

public void Clear()
{
_objectArray.Clear();
}

public bool Contains(T item)
{
return _objectArray.Contains(item);
}

public void CopyTo(T[] array, int arrayIndex)
{
_objectArray.CopyTo(array, arrayIndex);
}

public bool Remove(T item)
{
return _objectArray.Remove(item);
}

public int Count
{
get { return _objectArray.Count; }
}

public bool IsReadOnly
{
get { return false; }
}

public List<T> GetObjectArray()
{
return _objectArray;
}

public void Sort()
{
_objectArray.Sort();
}
}

There is more too this class obviously. It's being used as a base class for
strongly typed collections of different types, but these are all the relevant
methods I think. As you can see, for the methods in the IList, IEnumerable,
and IEnumerator interfaces, the methods just call the same methods on the
_objectArray list.

So, I try to execute code on a windows form like this

ObjectCollection<MyCustomObject> col = new ObjectCollection<MyCustomObject>;
dataGridView1.DataSource = col;

MyCustomObject is a custom object with about 10 properties, so each property
should be matched to a column in the DataGridView upon binding. With the
code above, nothing is displayed in the grid, however, if I change the code
to this

ObjectCollection<MyCustomObject> col = new ObjectCollection<MyCustomObject>;
dataGridView1.DataSource = col.GetObjectArray();

everything works fine. Note that GetObjectArray() is a function on the
ObjectCollection class that just returns the internal IList<T>.
 
J

Jon Skeet [C# MVP]

Jeff said:
Sure. Unfortunately I cannot view your link due to firewall restrictions
where I work, but I have since fixed the exception, and now when I bind to
the control (DataGridView in this case), the grid is not populated. Anyway,
here is the basic outline of the code:

<snip>

Well, here's a problem:
object IEnumerator.Current
{
get { return _objectArray.GetEnumerator().Current; }
}

bool IEnumerator.MoveNext()
{
return _objectArray.GetEnumerator().MoveNext();
}

What do you expect to happen if you call MoveNext() and then Current?
Instead of giving you the first value, it'll throw an exception -
because you've fetched a new enumerator in each call.

<snip>
 
G

Guest

Jon, I think I see what you mean, but wouldn't _objectArray.GetEnumerator()
return the same object each time?
 
J

Jon Skeet [C# MVP]

Jeff said:
Jon, I think I see what you mean, but wouldn't _objectArray.GetEnumerator()
return the same object each time?

No. That would be a really bad idea. For instance, it would mean that:

o Two threads couldn't iterate through a collection at the same time
o You couldn't iterate in a "nested" fashion (eg to create a list of
pairs of elements)
o Unless Reset were called each time you started iterating, you
wouldn't have the faintest idea what was going on.

GetEnumerator() should always return a new, independent enumerator.
 
G

Guest

Yeah, you're right. It wouldn't make much sense for GetEnumerator to return
the same object each time called.

I took your advice and change my IEnumerable/IEnumerator implementation to
this:

IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
//_enumerator is a private class member
_enumerator = _objectArray.GetEnumerator();
return _enumerator;
}

IEnumerator IEnumerable.GetEnumerator()
{
//_enumerator is a private class member
_enumerator = _objectArray.GetEnumerator();
return _enumerator;
}

T System.Collections.Generic.IEnumerator<T>.Current
{
get { return _enumerator.Current; }
}

object IEnumerator.Current
{
get { return _enumerator.Current; }
}

bool IEnumerator.MoveNext()
{
return _enumerator.MoveNext();
}

void IEnumerator.Reset()
{
throw new Exception("The method or operation is not implemented.");
}

now, I can bind to a grid through the BindingSource object, i.e. my code is

BindingSource bs = new BindingSource();
bs.DataSource = myCollection;
myDataGridView.Datasource = bs;

From what I've ready though, it seems BindingSource just creates an internal
List object, and the grid is actually binding to that List object. So, it's
not different than if I use my code's GetObjectArray() method to bind. There
still appears to be something wrong with my IEnumerable interface
implementation.
 
J

Jon Skeet [C# MVP]

Jeff said:
Yeah, you're right. It wouldn't make much sense for GetEnumerator to return
the same object each time called.

I took your advice and change my IEnumerable/IEnumerator implementation to
this:

<snip>

That's still not right though - it links calling MoveNext() on your
class to calling MoveNext() on whichever enumerator was last returned.
It may work for the moment, but it's not really right. Without
analysing it particularly closely, I think your
Current/MoveNext()/Reset() implementations are current, but
GetEnumerator() should just return _objectArray.GetEnumerator().

now, I can bind to a grid through the BindingSource object, i.e. my code is

BindingSource bs = new BindingSource();
bs.DataSource = myCollection;
myDataGridView.Datasource = bs;

From what I've ready though, it seems BindingSource just creates an internal
List object, and the grid is actually binding to that List object. So, it's
not different than if I use my code's GetObjectArray() method to bind. There
still appears to be something wrong with my IEnumerable interface
implementation.

Maybe the above will help. Do you need to implement *all* those
interfaces? Could you get away with doing *either* IEnumerator *or*
IEnumerable? (And the generic version of whichever one?)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top