【问题标题】:How does foreach casts objects to specified types?foreach 如何将对象转换为指定类型?
【发布时间】:2013-01-15 20:16:18
【问题描述】:

我对@9​​87654323@ 中的foreach 行为有疑问。

我的自定义类实现了自定义GetEnumerator。此方法返回另一个object,它可以隐式转换为string

但是,如果我执行foreach(string s in customClass),它会在运行时失败(“无法将 .. 类型的对象转换为字符串”)。

但是,如果我这样做 string x = new B(),它就像一个魅力。

注意:我在这里没有什么特别需要实现的,我只是想了解发生了什么。我对这种-泛型行为特别感兴趣。

有什么想法吗?我缺少哪些基础知识?

复制此代码的代码:

public class A : IEnumerable
{
    #region IEnumerable Members

    public IEnumerator GetEnumerator()
    {
        yield return new B();
    }

    #endregion
}

public class B
{
    public static implicit operator string( B b )
    {
        return "to-stringed implicit";
    }
}

// CODE:

A a = new A();

// Works.
B b = new B();
string xxx = b;

// Doesnt work.
foreach( string str in a )
{
}

【问题讨论】:

  • 你的隐式运算符 onB 是地狱之门;)
  • “foreach 语句”部分(C# 5.0 规范的 8.8.4) - 它准确涵盖了在 foreach 中执行和未执行的内容(以及它转换为的代码)。该文档通常位于您的“Program Files (x86)\Microsoft Visual Studio XX.0\VC#\Specifications\YYYY”文件夹中。
  • @TimSchmelter 我已经说得很清楚了,这段代码与实际使用无关。我只是想知道,为什么在 foreach 中不调用隐式/显式转换。也许有人可以让我知道如何更清楚地写问题,以便人们不再指出这些事情。

标签: c# foreach ienumerable


【解决方案1】:

基于 C# 规范中的“foreach 语句”部分(C# 5.0 规范的 8.8.4)我相信您的情况属于“枚举类型具有返回正确对象的 GetEnumerable”部分 - 没有隐式转换来确定元素的类型(如果类上没有唯一的 GetEnumerable,则有一些)

集合类型为X,枚举类型为E,元素类型为Current属性的类型。

在您的情况下,foreach 被翻译成以下代码(删除 try/finally):

// foreach( string str in items )
//    embedded-statement


IEnumerator enumerator = items.GetEnumerator();
while (enumerator.MoveNext()) 
  {
    string str = (string)(Object)enumerator.Current; // **
    embedded-statement
  }
}

请注意,在** 行上,要转换的对象类型为Object(由enumerator.Current 的结果确定,在非泛型Object 的情况下为Object。如您所见没有提及您的 B 类型将允许您进行隐式转换。

注意:规范可以在正常的 VS C# 安装中找到,位于以下位置: “Program Files (x86)\Microsoft Visual Studio XX.0\VC#\Specifications\YYYY”(XX 是 VS2010 的版本 10,VS2012 的版本为 11,YYYY 是 EN-US 的区域设置 1033)。

【讨论】:

  • 谢谢。我相信罗林斯的回答有点复杂。
  • @NeverStopLearning,Rawlings 的答案绝对更容易阅读和更正......如果你的昵称具有任何价值,我建议查看规范本身以查看源代码的解释(还涵盖与 GetEnumerator 的非接口版本的匹配如何工作)。
  • 确实,这就是为什么我以前也赞成您的回答。我会检查规范 - 我从来不知道存在这样的文件。
【解决方案2】:

只有在编译器认为可以在编译时使用时,才能使用您的隐式转换:

B b = new B();
string str = b;

不能在运行时使用:

B b = new B();
object obj = b;
string str = obj; // will fail at run-time

基本上,这是因为查看所有objstring 可能有效的转换的成本太高了。 (见this Eric Lippert blog post)。


您的IEnumerator 返回对象,因此调用foreach (string str in a) 会尝试在运行时将object 转换为B

var e = a.GetEnumerator();
e.MoveNext();
object o = e.Current;
string str = o; // will fail at run-time

如果您改用foreach(B item in a) { string str = item; ... },则运行时转换是从objectB(这是可行的,因为每个对象一个B),以及从@987654335 的转换@到str可以由编译器制作。

var e = a.GetEnumerator();
e.MoveNext();
object o = e.Current;
B item = o;        // will work at run-time because o _is_ a B
string str = item; // conversion made by the compiler

解决此问题的另一种方法是使A 实现IEnumerable<B> 而不仅仅是IEnumerable。然后,foreach (string str in a) 更多地翻译为

var e = a.GetEnumerator();
e.MoveNext();
B b = e.Current; // not object!
string str = b;  // conversion made by the compiler

因此编译器可以进行转换,而无需更改 foreach 循环。

【讨论】:

  • 谢谢。您的答案是唯一真正说明为什么会发生这种情况的答案。
【解决方案3】:

foreach 方法不需要你实现IEnumerable。所需要的只是你的类有一个名为GetEnumerator的方法:

public class A
{
    public IEnumerator GetEnumerator()
    {
        yield return new B();
    }
}

然后你可以在foreach 中使用它:

A a = new A();
foreach (B str in a)
{
    Console.WriteLine(str.GetType());
}

但是 foreach 语句不会调用隐式操作符。您必须手动执行此操作:

foreach (B item in a)
{
    string str = item;
    // use the str variable here
}

【讨论】:

  • 这和我在看这个时想出的 fix 相同,这可能是一个愚蠢的问题,但为什么不调用隐式运算符呢?跨度>
  • 因为这就是 C# 语言的设计者决定实现它的方式。我不是其中之一,所以不能给你确切的原因。
  • @DarinDimitrov 我试图说明为什么不会根据 C# 规范进行转换...移动下一步并解释为什么为规范选择了一组特定的规则是它得到“不能给出确切的理由”:)
  • 谢谢。我本质上是在问为什么会发生这种情况,而不是在问如何解决它(正如您建议进行手动转换)。关于不必实现 IEnumerable 的好处,我不知道。但是我认为这没什么区别,因为 IEnumerable 只是一种方法:GetEnumerator。
猜你喜欢
  • 2018-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-02
  • 2018-11-27
  • 2021-07-21
  • 2019-04-10
相关资源
最近更新 更多