我正在尝试使一些代码更具可读性。例如foreach(var row in table) {...}
而不是foreach(DataRow row in table.Rows) {...}
。
为此,我创建了一个扩展方法:
namespace System.Data {
public static class MyExtensions {
public static IEnumerable<DataRow> GetEnumerator( this DataTable tbl ) {
foreach ( DataRow r in tbl.Rows ) yield return r;
}
}
}
但是编译器仍然抛出foreach statement cannot operate on variables of type 'System.Data.DataTable' because 'System.Data.DataTable' does not contain a public definition for 'GetEnumerator'
。
为了确认我正确实现了扩展方法,我尝试了以下代码,编译器没有问题。
for ( IEnumerator<DataRow> enm = data.GetEnumerator(); enm.MoveNext(); ) {
var row = enm.Current;
...
}
在您说这是因为IEnumerator
或IEnumerator<DataRow>
未实现之前,请考虑以下内容进行编译:
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; }
}
答案 0 :(得分:40)
到目前为止,其他答案中存在许多混淆。 (虽然Preston Guillot的答案相当不错,但实际上并没有指出这里发生的事情。)让我试着澄清一下。
首先关闭,你只是运气不好。 C#要求foreach语句中使用的集合:
GetEnumerator
。IEnumerable
(当然,IEnumerable<T>
需要IEnumerable
)结果是集合类型必须以某种方式实际实现 GetEnumerator
。提供扩展方法不会削减它。
这很不幸。在我看来,当C#团队向C#3添加扩展方法时,他们应该修改现有功能,例如foreach
(甚至可能using
!)来考虑扩展方法。但是,在C#3发布周期中,时间表非常紧张,并且任何未及时实现LINQ的额外工作项都可能被削减。我不记得设计团队在这一点上说了什么,我再也没有笔记了。
这种不幸的情况是语言成长和发展的结果;旧版本是为他们的时间需求而设计的,新版本必须在此基础上构建。如果,反事实地,C#1.0有扩展方法和泛型,那么foreach
循环可以像LINQ一样设计:作为简单的句法转换。但事实并非如此,现在我们仍然坚持使用预先通用的预扩展方法设计。
第二,其他答案和评论中似乎有一些错误信息,说明使foreach
工作的确切要求。您无需实施IEnumerable
。有关此常被误解的功能的更多详细信息,请参阅my article on the subject。
第三,似乎有一些问题是这个行为是否实际上是由规范证明的。它是。规范没有明确地指出在这种情况下不考虑扩展方法,这是不幸的。但是,规范对发生的事情非常清楚:
编译器首先为GetEnumerator
执行成员查找。成员查找算法在7.3节中详细记录,成员查找不考虑扩展方法,仅实际成员。扩展方法仅在常规重载解析失败后被视为 ,并且我们还没有达到重载解析。 (是的,扩展方法由成员访问考虑,但成员访问和成员查找是不同的操作。)
如果成员查找无法找到方法组,则尝试匹配该模式将失败。因此编译器永远不会继续算法的重载解析部分,因此永远不会有机会考虑扩展方法。
因此,您描述的行为与指定的行为一致。
如果您想要准确理解编译器如何分析foreach
语句,我建议您仔细阅读规范的第8.8.4节。
第四,我鼓励您花时间以其他方式为您的计划增加价值。
令人信服的好处foreach (var row in table)
在
foreach(var row in table.Rows)
对于开发人员来说很小,对客户来说是不可见的。花时间添加新功能或修复错误或分析性能,而不是将已经完全清晰的代码缩短五个字符。
答案 1 :(得分:4)
测试类中的GetEnumerator方法不是静态的,扩展方法是。这不会编译:
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()) { }
}
}
为了使foreach语法糖起作用,您的类必须公开一个公共GetEnumerator 实例方法。
答案 2 :(得分:0)
一些offtopic:如果你想做更可读的写
foreach ( DataRow r in tbl.Rows ) yield return r;
作为
foreach (DataRow row in tbl.Rows)
{
yield return row;
}
现在你的问题..试试这个
public static IEnumerable<T> GetEnumerator<T>(this DataTable table)
{
return table.Rows.Cast<T>();
}
答案 3 :(得分:0)
您的扩展程序相当于:
public static IEnumerable<TDataRow> GetEnumerator<TDataRow>( this DataTable tbl ) {
foreach ( TDataRow r in tbl.Rows ) yield return r;
}
GetEnumerator<TDataRow>
与GetEnumerator
这会更好用:
public static IEnumerable<DataRow> GetEnumerator( this DataTable tbl ) {
foreach (DataRow r in tbl.Rows ) yield return r;
}
答案 4 :(得分:0)
在foreach语句中,编译器正在寻找GetEnumerator的实例方法。因此类型(此处为DataTable)必须实现IEnumerable。它永远不会找到你的扩展方法,因为它是静态的。您必须在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())
.....
为避免混淆,我建议您为扩展方法使用不同的名称。也许像GetRows()
答案 5 :(得分:0)
当前建议通过扩展将GetEnumerator添加到C#https://github.com/dotnet/csharplang/issues/3194
答案 6 :(得分:0)
这在C#9中是可能的。proposal已经签入。您可以在Language Feature Status - C# 9中进行验证,何时可以在预览版本中使用它。
在提案的详细设计部分中,内容为:
否则,确定类型“ X”是否具有适当的 GetEnumerator扩展方法
using System;
using System.Collections.Generic;
public static class MyExtensions
{
// Note: ranges aren't intended to work like this. It's just an example.
public static IEnumerable<int> GetEnumerator(this Range range)
{
// .. do validation ..
for (var i = range.Start.Value; i <= range.End.Value; i++)
{
yield return i;
}
}
}
public class ExtensionGetEnumerator
{
public void Method()
{
var range = 1..2;
foreach (var i in range.GetEnumerator())
{
Console.WriteLine($"Print with explicit GetEnumerator {i}");
}
// The feature is in progress, scheduled for C# 9, the below does not compile yet
//foreach (var i in range)
//{
// Console.WriteLine($"Print with implicit GetEnumerator {i}");
//}
}
}
答案 7 :(得分:-6)
foreach
中的对象集必须实现System.Collections.IEnumerable
或System.Collections.Generic.IEnumerable<T>
。
如果您非常强烈地希望启用此功能,那么您可以创建一个实现IEnumerable
的包装器类,并指向您DataTable
。或者,您可以在新类中继承DataTable
并实现IEnumerable
。
代码可读性通常是个人偏好。我个人认为您对foreach
声明的更改不太可读(但我相信很多人都同意你的观点)。