Why can't I do foreach (var Item in DataTable.Rows)?
Asked Answered
L

3

31

Is there a reason why I can't do the following:

foreach (var Item in DataTable.Rows) {

rather than having to do

foreach (DataRow Item in DataTable.Rows) {

I would have thought this was possible, like it is on other datatypes. For example:

foreach (var Employee in Staff) { // string[] Staff etc...

When I try the first foreach loop, I get the the error CS0021: Cannot apply indexing with [] to an expression of type 'object'.

Why can't the compiler figure out that .Rows returns a collections of DataRows?

Ledesma answered 24/2, 2010 at 12:6 Comment(0)
C
30

Rows effectively returns IEnumerable (DataRowCollection), so the compiler can only pick object as the type for var. Use Rows.Cast<DataRow> if you want to use var.

Cast is defined on Enumerable, so you have to include System.Linq.

Complement answered 24/2, 2010 at 12:9 Comment(3)
Were datatables before generics?Marnamarne
@Arnis: Absolutely, DataTable has been in since 1.0.Emanate
So - i guess that's the real reason. Ty Jon for clarification. :)Marnamarne
E
25

Brian is absolutely right about the reason for this, but there's a simpler way of avoiding it: use DataTableExtensions.AsEnumerable():

foreach (var row in DataTable.AsEnumerable())
{
    ...
}
Emanate answered 24/2, 2010 at 12:10 Comment(3)
Thank you for such a nifty solution :) I have kept the Accepted Answer as Brian's but will be using yours. It's time a read up about Enumerables a bit more...Ledesma
I'd say the real solution to the problem would be to not use var in this instance...Edp
@ck: Agreed, but it's worth knowing about AsEnumerable in order to tie in with other LINQ methods.Emanate
L
2

Why can't the compiler figure out that .Rows returns a collections of DataRows?

Because of the way the DataTable Rows (DataRowCollection) works with enumeration, and also partly to do with the way foreach works, and how ancient it all is.

First a bit about foreach:

foreach isn't really a thing; it's more like a language shortcut for a while loop that gets an enumerator on an object and repeatedly calls MoveNext, MoveNext, MoveNext ... until MoveNext returns false, at which point it stops looping. The first thing the loop body of the while does is call Current on the enumerator, and and loads the response into the variable you specified in the foreach declaration. The body of the loop you wrote is placed into the body of the while loop, after this first bit. To you it looks like foreach is a real loop. In reality it's like a find/replace exercise by the compiler. Lots of things in C# are this way - new features and syntax reductions are just a find/replace way of doing the code the old way.

To be foreachable a class doesn't have to implement any interface or be any particular type, it simply has to have a method called GetEnumerator() that returns "something that can be enumerated".

"Something that can be enumerated" is defined as "a class that has a bool MoveNext() method and a Current property that returns any kind of object". Again, this "something that can be enumerated" doesn't have to implement any interface, or be any particular type; it just have to have one method and one property with certain name(s) and particular signature(s). The whole thing is like magic and there are no constraints on it anywhere:

public class ForeachableThing{
  public EnumeratorSchmenumerator GetEnumerator(){
    return new EnumeratorSchmenumerator();
  }
}

public class EnumeratorSchmenumerator{
  public DateTime Current {
    get{ return DateTime.Now; }
  }
  public bool MoveNext() {
    return DateTime.Now.Hour == 0;
  }
}

You can foreach this millions of times to get the current time, between midnight and 1am. The enumeration doesn't need to have a collection or move anywhere; it just has to return values as asked and return true or false when told to move. I outline this just in case you think "but surely something would have to implement some IEnumerable.. and that would mean there's some typing in there somewhere and..". No - ever since datatable was invented it's been possible to enumerate things purely based on whether there is a certain method/property on them or not


Skipping forward to DataTable.Rows; this is a collection; a DataRowCollection to be precise. It does implement the IEnumerable interface by dint of the fact that it inherits from InternalDataCollectionBase, which implements IEnumerable - but that in itself is only an interface that states "The class must have a GetEnumerator() method that returns an IEnumerator", and IEnumerator is an interface that enforces "must have a Current property that returns object and a MoveNext() that returns bool".

It wasn't necessary to implement these interfaces to be foreachable, but it helps..

..and maybe you then spotted it: implementing IEnumerator requires that the Current property be an object.

This is a compiler error:

public class EnumeratorSchmenumerator : IEnumerator //look! new bit!
{
  public DateTime Current {
    get{ return DateTime.Now; }
  }
  public bool MoveNext() {
    return DateTime.Now.Hour == 0;
  }
}

'EnumeratorSchmenumerator' does not implement interface member 'IEnumerator.Current'. [The existing] 'EnumeratorSchmenumerator.Current' cannot implement 'IEnumerator.Current' because it does not have the matching return type of 'object'

So in choosing to have the DataRowCollection implement IEnumerable, they were forced to write a .GetEnumerator() method that returned an IEnumerator, and that in turn forces the Current to be an object

There's a DataRow inside that object, but the compiler doesn't know that, so it types the var as object.

When you say foreach(DataRow r in dt.Rows) the code C# writes behind the scenes to expand the foreach will include a cast to DataRow.

With the var form, it's like you wrote foreach(object r in dt.Rows)

Lucy answered 8/7, 2020 at 11:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.