locked
Unable to cast object of type 'Enumerator[MyObject]' to type System.Collections.Generic.IEnumerator`1...

    Question

  • 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.


    Unable to cast object of type 'Enumerator[Cactus.Commerce.Contracts.BusinessItems.Catalog.Category]' to type 'System.Collections.Generic.IEnumerator`1[System.Collections.Generic.List`1[Cactus.Commerce.Contracts.BusinessItems.Catalog.Category]]'.

    Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

    Exception Details: System.InvalidCastException: Unable to cast object of type 'Enumerator[Category]' to type 'System.Collections.Generic.IEnumerator`1[System.Collections.Generic.List`1[Category]]'.

    Source Error:

    Line 523:        internal DataTable FillDataTable()
    Line 524:        {
    Line 525:            IEnumerator<T> enumerator = (IEnumerator<T>)_collection.GetEnumerator();
    Line 526:            DataTable dt = CreateDataTable();
    Line 527:            while (enumerator.MoveNext())

    Source File: CategorySiteMapGenerator.cs    Line: 525

    Stack Trace:

    [InvalidCastException: Unable to cast object of type 'Enumerator[Category]' to type 'System.Collections.Generic.IEnumerator`1[System.Collections.Generic.List`1[Category]]'.]
       PrototypeSite.SiteMap.CategoryDataSet`1.FillDataTable() in CategorySiteMapGenerator.cs:525
       PrototypeSite.SiteMap.CategoryDataSet`1.CreateDataSet() in CategorySiteMapGenerator.cs:518
       PrototypeSite.SiteMap.CategorySiteMapGenerator.CreateSiteMap() in CategorySiteMapGenerator.cs:109
       PrototypeSite.TestSiteMap.btnGenerateSiteMap_Click(Object sender, EventArgs e) in TestSiteMap.aspx.cs:34
       System.Web.UI.WebControls.Button.OnClick(EventArgs e) +105
       System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) +107
       System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) +7
       System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +11
       System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData) +33
       System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +5102
    


    Version Information: Microsoft .NET Framework Version:2.0.50727.832; ASP.NET Version:2.0.50727.832

     

    The List<Category> value passed into the generic class (cited at the URL) via its constructor is actually being generated by a class called via WCF.  Also, when I check the value of _collection.GetEnumerator() at line 525 in the listings above, I get an InvalidOperationException...as if whatever's coming back from the class behind WCF is not really a collection, or IEnumerable!  Yet when I step into the code at that point, I do see a list of Category objects.

     

    Can anyone assist with this and tell me what might be causing my trouble?  How do I convert the generic List type to a DataSet as intended if my approach here is off-base.

     

    Gratefully,

    RH

     

     

     

    Wednesday, August 01, 2007 11:21 PM

Answers

  •  

    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

    Thursday, August 02, 2007 3:58 PM

All replies

  •  

    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

     

    Thursday, August 02, 2007 12:33 PM
  • 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

     

     

     

    Thursday, August 02, 2007 3:16 PM
  •  

    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

    Thursday, August 02, 2007 3:58 PM
  • 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

     

     

    Thursday, August 02, 2007 4:16 PM
  • 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

     

    Thursday, August 02, 2007 4:23 PM