为什么我不能使用 foreach (var Item in DataTable.Rows)?

31

我不能这样做的原因是什么:

foreach (var Item in DataTable.Rows) {

与其不得不做某事,

foreach (DataRow Item in DataTable.Rows) {

我本以为这是可能的,就像其他数据类型一样。例如:

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

当我尝试使用第一个foreach循环时,我遇到了错误CS0021: 不能将[]索引应用于类型为“object”的表达式

为什么编译器不能确定.Rows返回的是DataRow集合?

3个回答

30

Rows 实际上返回的是 IEnumerable (DataRowCollection),因此编译器只能选择将 var 的类型作为 object。如果想要使用 var,请使用 Rows.Cast<DataRow>

Cast 定义在 Enumerable 上,因此你需要包含 System.Linq。


泛型出现之前有数据表格吗? - Arnis Lapsa
@Arnis:当然,DataTable自1.0版本以来就一直存在。 - Jon Skeet
所以 - 我猜那就是真正的原因。感谢Jon的澄清。 :) - Arnis Lapsa

25

关于这个问题,Brian说的是绝对正确的,但是有一个更简单的方法可以避免它:使用DataTableExtensions.AsEnumerable()

foreach (var row in DataTable.AsEnumerable())
{
    ...
}

谢谢你提供这么棒的解决方案 :) 我已经将Brian的答案标记为采纳,但我会使用你的。现在是时候多了解一下可枚举对象了... - GateKiller
8
我认为这个问题的真正解决方案是在这种情况下不使用"var"。 - cjk
1
@ck:同意,但值得知道AsEnumerable以便与其他LINQ方法相结合。 - Jon Skeet

2
为什么编译器不能理解.Rows返回的是DataRow集合呢?
这是因为DataTable.Rows(DataRowCollection)与枚举的方式有关,部分原因也与foreach的工作方式以及其古老的特性有关。
首先,我们来谈一下foreach:
foreach不是一个实际存在的东西,它更像是一个语言快捷方式,用于在对象上获取一个枚举器并重复调用MoveNext,MoveNext,MoveNext…直到MoveNext返回false,此时它将停止循环。while循环体的第一件事就是在枚举器上调用Current,并将响应加载到您在foreach声明中指定的变量中。您编写的循环体将放置在while循环体的主体中,位于此第一部分之后。对您而言,看起来像是foreach是一个真正的循环。实际上,它就像编译器中的查找/替换练习。C#中的许多内容都是这样 - 新功能和语法缩减只是以查找/替换的方式执行旧代码的方式。
要使类可以使用foreach,它不必实现任何接口或成为任何特定类型,它只需要具有一个名为GetEnumerator()的方法,该方法返回“可枚举的东西”。
“可以枚举的东西”被定义为“具有bool MoveNext()方法和返回任何类型对象的Current属性的类”。同样,“可以枚举的东西”不必实现任何接口或成为任何特定类型;它只需具有一个名称和特定签名的方法和属性。整个过程就像魔法一样,没有任何限制。
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;
  }
}

您可以使用foreach来获取当前时间,从午夜到凌晨1点之间。枚举不需要有集合或移动任何地方;它只需要按要求返回值,并在被告知移动时返回true或false。我提出这个问题只是为了防止您认为“但肯定有些东西必须实现一些IEnumerable..,那就意味着在某个地方有一些打字和..”。不是这样的-自datatable发明以来,纯粹基于它们是否具有某些方法/属性,已经可以枚举事物了。
跳到DataTable.Rows;这是一个集合;确切地说是一个DataRowCollection。它通过继承自InternalDataCollectionBase实现了IEnumerable接口,该接口本身仅是一个声明“该类必须具有返回IEnumerator的GetEnumerator()方法”的接口,IEnumerator是一个强制执行“必须具有返回object的Current属性和返回bool的MoveNext()”的接口。
不必实现这些接口即可进行foreach,但这有助于..
..也许您随后会注意到:实现IEnumerator要求Current属性为对象。
这是编译器错误:
public class EnumeratorSchmenumerator : IEnumerator //look! new bit!
{
  public DateTime Current {
    get{ return DateTime.Now; }
  }
  public bool MoveNext() {
    return DateTime.Now.Hour == 0;
  }
}

"'EnumeratorSchmenumerator'未实现接口成员'IEnumerator.Current'。" "现有的'EnumeratorSchmenumerator.Current'无法实现'IEnumerator.Current',因为它没有匹配的返回类型'object'。"
因此,选择让DataRowCollection实现IEnumerable时,他们被迫编写一个返回IEnumerator.GetEnumerator()方法,这反过来又强制要求Current是一个object
那个对象里面有一个DataRow,但编译器不知道,所以会将var类型定义为object
当你使用foreach(DataRow r in dt.Rows)语句时,C#在后台自动添加一个转换为DataRow的语句。
var形式中,就像你写了foreach(object r in dt.Rows)一样。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接