.NET Forum / .NET Framework / New Users / January 2006
Custom collection implementing IEnumerable<T>
|
|
Thread rating:  |
Jeff - 03 Jan 2006 21:57 GMT 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
Jon Skeet [C# MVP] - 03 Jan 2006 22:12 GMT > 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 [quoted text clipped - 20 lines] > > 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?
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Jeff - 04 Jan 2006 13:22 GMT 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.
> > 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 [quoted text clipped - 25 lines] > > What does the stack trace say? Jon Skeet [C# MVP] - 04 Jan 2006 19:26 GMT > myControl is not null, either. > [quoted text clipped - 7 lines] > 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.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Jeff - 04 Jan 2006 20:15 GMT 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>.
> > myControl is not null, either. > > [quoted text clipped - 13 lines] > See http://www.pobox.com/~skeet/csharp/complete.html for details of > what I mean by that. Jon Skeet [C# MVP] - 04 Jan 2006 20:24 GMT > 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 > { [quoted text clipped - 5 lines] > 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>
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Jeff - 04 Jan 2006 20:55 GMT Jon, I think I see what you mean, but wouldn't _objectArray.GetEnumerator() return the same object each time?
> > 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 [quoted text clipped - 20 lines] > > <snip> Jon Skeet [C# MVP] - 04 Jan 2006 21:42 GMT > 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.
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Jeff - 05 Jan 2006 13:40 GMT 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.
> > Jon, I think I see what you mean, but wouldn't _objectArray.GetEnumerator() > > return the same object each time? [quoted text clipped - 8 lines] > > GetEnumerator() should always return a new, independent enumerator. Jon Skeet [C# MVP] - 05 Jan 2006 18:18 GMT > 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(). <snip>
> now, I can bind to a grid through the BindingSource object, i.e. my code is > [quoted text clipped - 7 lines] > 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?)
 Signature Jon Skeet - <skeet@pobox.com> http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet If replying to the group, please do not mail me too
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|