I'm trying to make some code more readable. For Example foreach(var row in table) {...}
rather than foreach(DataRow row in table.Rows) {...}
.
To do this I created an extension method:
namespace System.Data {
public static class MyExtensions {
public static IEnumerable<DataRow> GetEnumerator( this DataTable tbl ) {
foreach ( DataRow r in tbl.Rows ) yield return r;
}
}
}
But the compiler still throws foreach statement cannot operate on variables of type 'System.Data.DataTable' because 'System.Data.DataTable' does not contain a public definition for 'GetEnumerator'
.
To confirm that I implemented the extension method appropriately I tried the following code instead and the compiler had no problem with it.
for ( IEnumerator<DataRow> enm = data.GetEnumerator(); enm.MoveNext(); ) {
var row = enm.Current;
...
}
Before you say that it is because IEnumerator
or IEnumerator<DataRow>
is not implemented, consider that the following does compile:
public class test {
public void testMethod() {
foreach ( var i in new MyList( 1, 'a', this ) ) { }
}
}
public class MyList {
private object[] _list;
public MyList( params object[] list ) { _list = list; }
public IEnumerator<object> GetEnumerator() { foreach ( var o in _list ) yield return o; }
}
There is plenty of confusion in the other answers so far. (Though Preston Guillot's answer is pretty good, it does not actually put a finger on what's going on here.) Let me try to clarify.
First off, you are simply out of luck. C# requires that the collection used in a foreach statement either:
- Implement a public
GetEnumerator
that matches the required pattern.
- Implement
IEnumerable
(and of course, IEnumerable<T>
requires IEnumerable
)
- Be dynamic, in which case we simply kick the can down the road and do the analysis at runtime.
The upshot is that the collection type must actually implement the GetEnumerator
one way or the other. Providing an extension method does not cut it.
This is unfortunate. In my opinion, when the C# team added extension methods to C# 3 they should have modified existing features such as foreach
(and perhaps even using
!) to consider extension methods. However, the schedule was extremely tight during the C# 3 release cycle and any extra work items that did not get LINQ implemented on time were likely to be cut. I do not recall precisely what the design team said on this point and I don't have my notes anymore.
This unfortunate situation is the result of the fact that languages grow and evolve; old versions are designed for the needs of their time, and new versions have to build on that foundation. If, counterfactually, C# 1.0 had had extension methods and generics then the foreach
loop could have been designed like LINQ: as a simple syntactic transformation. But it was not, and now we are stuck with the legacy of pre-generic, pre-extension-method design.
Second, there seems to be some misinformation in other answers and comments about what precisely is required to make foreach
work. You are not required to implement IEnumerable
. For more details on this commonly misunderstood feature, see my article on the subject.
Third, there seems to be some question as to whether this behaviour is actually justified by the specification. It is. The specification does not explicitly call out that extension methods are not considered in this case, which is unfortunate. However, the specification is extremely clear on what happens:
The compiler begins by doing a member lookup for GetEnumerator
. The member lookup algorithm is documented in detail in section 7.3, and member lookup does not consider extension methods, only actual members. Extension methods are only considered after regular overload resolution has failed, and we haven't gotten to overload resolution yet. (And yes, extension methods are considered by member access, but member access and member lookup are different operations.)
If member lookup fails to find a method group then the attempt to match the pattern fails. The compiler therefore never goes on to the overload resolution portion of the algorithm, and therefore never has a chance to consider extension methods.
Therefore the behaviour you describe is consistent with the specified behaviour.
I advise you to read section 8.8.4 of the specification very carefully if you want to understand precisely how a compiler analyzes a foreach
statement.
Fourth, I encourage you to spend your time adding value to your program in some other way. The compelling benefit of
foreach (var row in table)
over
foreach(var row in table.Rows)
is tiny for the developer and invisible to the customer. Spend your time adding new features or fixing bugs or analyzing performance, rather than making already perfectly clear code five characters shorter.
The GetEnumerator method in your test class is not static, the extension method is. This doesn't compile either:
class test
{
}
static class x
{
public static IEnumerator<object> GetEnumerator(this test t) { return null; }
}
class Program
{
static void Main(string[] args)
{
foreach (var i in new test()) { }
}
}
In order for the foreach syntax sugar to work your class must expose a public GetEnumerator instance method.
some offtopic: if you want to do it more readable write
foreach ( DataRow r in tbl.Rows ) yield return r;
as
foreach (DataRow row in tbl.Rows)
{
yield return row;
}
now to your problem.. try this
public static IEnumerable<T> GetEnumerator<T>(this DataTable table)
{
return table.Rows.Cast<T>();
}
Your extension is equivalent to:
public static IEnumerable<TDataRow> GetEnumerator<TDataRow>( this DataTable tbl ) {
foreach ( TDataRow r in tbl.Rows ) yield return r;
}
GetEnumerator<TDataRow>
is not the same method as GetEnumerator
This will work better:
public static IEnumerable<DataRow> GetEnumerator( this DataTable tbl ) {
foreach (DataRow r in tbl.Rows ) yield return r;
}
Within an foreach statement the compiler is looking for an instance method of GetEnumerator. Therefore the type (here DataTable) must implement IEnumerable. It will never find your extension method instead, because it is static. You have to write the name of your extension method in the foreach.
namespace System.Data {
public static class MyExtensions {
public static IEnumerable<DataRow> GetEnumerator( this DataTable table ) {
foreach ( DataRow r in table.Rows ) yield return r;
}
}
}
foreach(DataRow row in table.GetEnumerator())
.....
To avoid confusion, I would suggest using a different name for your extension method. Maybe something like GetRows()
The object collection in a foreach
must implement System.Collections.IEnumerable
or System.Collections.Generic.IEnumerable<T>
.
If you have a very strong desire to enable this then you could create a wrapper class which implements IEnumerable
and has a pointer to you DataTable
. Alternatively, you could inherit DataTable
in a new class and implement IEnumerable
.
Code readability is most often personal preference. I personally find your changes to the foreach
statement less readable (but I am sure there are many on SO who agree with you).