Ross Holder

My question is very specific. Consider the class enclosed in the article at this URL:

Then, consider the following excerpts from a class I'm working on:

Code Snippet

108 CategoryDataSet<List<Category>> dsCategory = new CategoryDataSet<List<Category>>(response.CategoryList);

109 this._neutralCategoryDataSet = dsCategory.CreateDataSet();

The following lines are from the class cited at the aforementioned URL, but are included here for convenience:

Code Snippet

515 internal DataSet CreateDataSet()

516 {

517 DataSet ds = new DataSet("GridDataSet");

518 ds.Tables.Add(FillDataTable());

519 return ds;

520 }

521

522

523 internal DataTable FillDataTable()

524 {

525 IEnumerator<T> enumerator = (IEnumerator<T>)_collection.GetEnumerator();

526 DataTable dt = CreateDataTable();

527 while (enumerator.MoveNext())

528 {

529 dt.Rows.Add(FillDataRow(dt.NewRow(), enumerator.Current));

530 }

531 return dt;

532 }

At line 109 in the listings here, an exception occurs, as described below:

Server Error in '/' Application.


Re: Visual C# Language Unable to cast object of type 'Enumerator[MyObject]' to type System.Collections.Generic.IEnumerator`1...

TaylorMichaelL

The error message, though, is telling you T is of type Category whereas _collection is a List<Category>. The conversion will fail. Let's identify the types in use here.

T = List<Category>

_collection = List<Category>

_collection.GetEnumerator() = IEnumerator<Category>

IEnumerator<T> = IEnumerator<List<Category>>

See the problem. You are assuming that IEnumerator<T> and _collection.GetEnumerator are the same type but they aren't. Since T is a list (based on the generic constraint) then IEnumerator<T> would allow you to enumerate a list of lists of T. However since _collection is the actual list its enumerator allows you to enumerate the elements (of T).

Your problems arise because you try to strongly type the enumerators but in the original code the author used the standard versions since they didn't really care about the underlying type.

Michael Taylor - 8/2/07

http://p3net.mvps.org





Re: Visual C# Language Unable to cast object of type 'Enumerator[MyObject]' to type System.Collections.Generic.IEnumerator`1...

Ross Holder

I see what you're saying....but we might have one additional problem you could add comment on: why is the enumerator of _collection returning an exception in the immediate window:

_collection.GetEnumerator()

{System.Collections.Generic.List<Category>.Enumerator}

[System.Collections.Generic.List<Category>.Enumerator]: {System.Collections.Generic.List<Category>.Enumerator}

Current: '_collection.GetEnumerator().Current' threw an exception of type 'System.InvalidOperationException'

In fact, it doesn't look to me as if we're gonna be casting much of anything to start with if _collection.GetEnumerator() itself returns only an exception. What's wierd about this is, you'll recall that on line 108 in the listings in my last post I instantiated the generic class CategoryDataSet<T> by using an instance of List<Category> "response.CategoryList" in the constructor. If, in the immediate window, I try a "response.CategoryList.GetEnumerator()", I get the following:

response.CategoryList.GetEnumerator()

{System.Collections.Generic.List<System.__Canon>.Enumerator}

Current: null

...which is what one would expect - a returned Enumerator.

So while there's clearly an issue with (new List<Category>()).GetEnumerator() returning a type other than IEnumerator<T>, I think there's another issue hold me back here....and help with this would be very much appreciated also.

If you are kind enough to respond - could I get you to use a few lines of code by way of an example soltuion It might help clarify things at my end a bit too.

Gratefully,

RH






Re: Visual C# Language Unable to cast object of type 'Enumerator[MyObject]' to type System.Collections.Generic.IEnumerator`1...

TaylorMichaelL

That's a good question. Be aware that enumerators are sort of special in how they work. They generally receive a list of items as a parameter. They then enumerate the list. To speed up processing and avoid wasting memory they generally reference the original list rather than cloning it. Because of this all enumerators are set up to throw an exception if the underlying collection changes. This is generally done by tracking the version # of the collection (which changes whenever the collection does). The result is an exception whenever the collection changes. Is that occurring in your case I don't know as I don't know how the code is being used. Given that you accept a list as input that list can not be changed while the class builds the data set.

Honestly though I think the code is a little overkill for something that should be relatively straightforward to accomplish. I would propose a simpler layout that should solve your problems IMHO. The biggest problem I have with the code is that it requires you to type a class instance when the type can be inferred. Furthermore creating an instance to call a single method is a waste. Static methods are designed for this. Here is my proposed solution that should do the same thing but hasn't been tested.

Code Snippet

public static class DataSetHelper

{

public static DataSet CollectionToDataSet ( ICollection items )

{

DataTable dt = new DataTable();

//Build the columns

PropertyInfo[] props = PopulateColumns(dt, typeof(T));

//Copy the elements

PopulateRows(dt, props, items);

//Since you want a DataSet (although a table will do)

DataSet ds = new DataSet();

ds.Tables.Add(dt);

return ds;

}

private static PropertyInfo[] PopulateColumns ( DataTable dt, Type typeItem )

{

//Get the properties defined for the type (we ignore non-public and

// static properties)

PropertyInfo[] props = typeItem.GetProperties(BindingFlags.GetProperty |

BindingFlags.Instance | BindingFlags.Public);

foreach (PropertyInfo prop in props)

{

//Should probably confirm that the property is something we can

//reasonably store in a data table but that is left as an exercise

dt.Columns.Add(prop.Name, prop.PropertyType);

};

return props;

}

private static void PopulateRows ( DataTable dt, PropertyInfo[] props,

ICollection items )

{

foreach (T item in items)

{

DataRow dr = dt.NewRow();

//Copy the property values (won't work for indexed properties)

foreach (PropertyInfo prop in props)

{

try

{

dr[prop.Name] = prop.GetValue(item, null);

} catch (Exception e)

{

//Do something meaningful

};

};

dt.Rows.Add(dr);

};

}

}

You would use it like this:

Code Snippet

DataSet ds = DataSetHelper.CollectionToDataSet(Process.GetProcesses());

I personally find it easier to understand.

Michael Taylor - 8/2/07

http://p3net.mvps.org





Re: Visual C# Language Unable to cast object of type 'Enumerator[MyObject]' to type System.Collections.Generic.IEnumerator`1...

Ross Holder

Wow! Well I really appreciate your help with this - and the lengths you've gone to on my behalf with this issue. Your solution seems simpler, and easier to follow, certainly. One immediate issue I saw in your code, though - you declared the class as "DataSetHelper" and not "DataSetHelper<T>". Did you not intend the latter There are "T"s embedded in a couple of places in your code....

Gratefully,

RH






Re: Visual C# Language Unable to cast object of type 'Enumerator[MyObject]' to type System.Collections.Generic.IEnumerator`1...

TaylorMichaelL

Interesting feature of the paste operation is that it removed the type parameters. Here is the corrected version.

Code Snippet

public static class DataSetHelper

{

public static DataSet CollectionToDataSet<T> ( ICollection<T> items )

{

DataTable dt = new DataTable();

//Build the columns

PropertyInfo[] props = PopulateColumns(dt, typeof(T));

//Copy the elements

PopulateRows<T>(dt, props, items);

//Since you want a DataSet (although a table will do)

DataSet ds = new DataSet();

ds.Tables.Add(dt);

return ds;

}

private static PropertyInfo[] PopulateColumns ( DataTable dt, Type typeItem )

{

//Get the properties defined for the type (we ignore non-public and

// static properties)

PropertyInfo[] props = typeItem.GetProperties(BindingFlags.GetProperty |

BindingFlags.Instance | BindingFlags.Public);

foreach (PropertyInfo prop in props)

{

//Should probably confirm that the property is something we can

//reasonably store in a data table but that is left as an exercise

dt.Columns.Add(prop.Name, prop.PropertyType);

};

return props;

}

private static void PopulateRows<T> ( DataTable dt, PropertyInfo[] props,

ICollection<T> items )

{

foreach (T item in items)

{

DataRow dr = dt.NewRow();

//Copy the property values (won't work for indexed properties)

foreach (PropertyInfo prop in props)

{

try

{

dr[prop.Name] = prop.GetValue(item, null);

} catch (Exception e)

{

//Do something meaningful

};

};

dt.Rows.Add(dr);

};

}

}

Michael Taylor - 8/2/07

http://p3net.mvps.org