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 foreach
able 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 foreach
able, 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)