foreach循环IEnumerable<T>总是调用非泛型枚举器。

3
我创建了一个对象并实现了接口 IEnumerable<Options>。 如果我尝试遍历我的对象,它可以正常工作,但变量不会被转换为 Options 类型而是 object
public class MyClass : IEnumerable<Options>
{
    // I just cut the non-relevant code. The real class is over 500 lines ...

    public List<Options> options = new List<Options>();

    IEnumerator<Options> IEnumerable<Options>.GetEnumerator()
    {
        return options.GetEnumerator();
    }

    public IEnumerator GetEnumerator()
    {
        return options.GetEnumerator();
    }
}

如何循环遍历:

foreach (var option in myClass)
{
    // Do something
}

typeof(option) 返回 object。但由于我实现了通用接口,所以希望将其返回为 Options。它确实是一个Options对象,我可以使用(Options)option进行显式转换,但应该自动转换。

foreach (Options option in myClass)
{
   // I want it to be casted automatically when using 'var'
}

3
您已经将通用的GetEnumerator方法从通用的IEnumerable<T>接口中进行了显式的接口实现。尝试将非泛型的IEnumerable.GetEnumerator改为显式接口实现,并正常声明泛型的GetEnumerator方法(即不作为显式接口实现)。我没有亲自测试/验证,但我相信这是您痛苦的根源... - user19858830
1
重新打开,因为它似乎与如何实现IEnumerable<T>不同。 - Tim Schmelter
1
@TimSchmelter 对我来说也是一样的,它展示了如何正确地将这两个接口结合在一起实现。 - Charlieface
@Charlieface:你说得对,如果使用这段代码,它也可以正常工作。public IEnumerator<Options> GetEnumerator(){ return options.GetEnumerator(); } - Tim Schmelter
这个回答解决了你的问题吗?如何实现IEnumerable<T> - Charlieface
3个回答

2

引用自C#语言参考,12.9.5 foreach语句:

编译时处理foreach语句,首先确定表达式的集合类型、枚举器类型和迭代类型。此确定过程如下:
- 如果表达式的类型X是数组类型,则从XIEnumerable接口有一个隐式引用转换(因为System.Array实现了该接口)。集合类型是IEnumerable接口,枚举器类型是IEnumerator接口,迭代类型是数组类型X的元素类型。 - 如果表达式的类型Xdynamic类型,则表达式到IEnumerable接口有一个隐式转换(§10.2.10)。集合类型是IEnumerable接口,枚举器类型是IEnumerator接口。如果local_variable_type指定为var标识符,则迭代类型为dynamic,否则为object。 - 否则,确定类型X是否具有适当的GetEnumerator方法: - 在类型X上执行成员查找,标识符为GetEnumerator,没有类型参数。如果成员查找没有产生匹配项,或者它产生了歧义,或者产生了不是方法组的匹配项,请按以下方法检查可枚举接口。如果成员查找产生除方法组或无匹配项之外的任何内容,则建议发出警告。 - 使用结果方法组和空参数列表执行重载解析。如果重载解析导致没有适用的方法,导致歧义,或者导致单个最佳方法但该方法为静态或不是公共的,则按以下方法检查可枚举接口。如果重载解析产生除了一个明确的公共实例方法或无适用方法之外的任何内容,则建议发出警告。 - 如果GetEnumerator方法的返回类型E不是类、结构或接口类型,则会生成错误,并且不采取进一步步骤。 - 在E上执行成员查找,标识符为Current,没有类型参数。如果成员查找没有产生匹配项,则结果是错误,或结果除了允许读取的公共实例属性之外的任何内容,则会生成错误,并且不采取进一步步骤。 - 在E上执行成员查找,标识符为MoveNext,没有类型参数。如果成员查找没有产生匹配项,则结果是错误,或结果不是方法组,则会生成错误,并且不采取进一步步骤。 - 对空参数列表执行方法组上的重载解析。如果重载解析导致没有适用的方法,导致歧义,或者导致单个最佳方法但该方法为静态或不是公共的,或其返回类型不是布尔值,则会生成错误,并且不采取进一步步骤。 - 集合类型是X,枚举器类型是E,迭代类型是Current属性的类型。 - 否则,检查可枚举接口: - 如果在所有类型Tᵢ中,都存在从XIEnumerable<Tᵢ>的隐式转换,则存在唯一的类型T,使得T不是动态类型,对于所有其他Tᵢ

在您的情况下,编译器在MyClass中找到了一个公共的GetEnumerator方法,这恰好是一个返回IEnumerator的方法,并且很高兴使用此方法进行foreach枚举,因为条件被满足。没有理由再向下检查可枚举接口,因此显式实现的IEnumerable<Options>.GetEnumerator方法被忽略。


0
foreach 中,编译器看不到 GetEnumerator 方法的泛型版本,因为您正在显式地实现它,因此如果没有将其强制转换为 IEnumerable<Options>,则会使用 IEnumerable 实现,从而产生 object
使用当前的实现,您必须执行以下操作:
foreach (var option in (IEnumerable<Options>) myClass)

那么option的类型是Options

你应该显式地实现非泛型版本,隐式地实现泛型版本。然后它就能按照你的期望工作:

public IEnumerator<Options> GetEnumerator()
{
    return options.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
    return options.GetEnumerator();
}

var option 的类型将是 Options

更新:对于那些对完整的 GetEnumerator() 解析算法感兴趣的人,该算法相当复杂,请参见 C# 语言规范,以及 C# 8.0 中的改进C# 9.0 中的改进


编译器看不到GetEnumerator方法的泛型版本,因为您是显式实现它,这并没有解释为什么编译器在两个都显式实现的情况下使用泛型版本。 - Theodor Zoulias
@TheodorZoulias 因为编译器优先考虑 IEnumerable<T>。寻找适当的 GetEnumerator 方法的搜索算法相当复杂,并在C#语言规范中有所描述。顺便说一下,您根本不需要公开任何 IEnumerable,只需给编译器一个适当的公共 GetEnumerator 方法,满足某些条件即可。 - Ondrej Tucny
1
@TheodorZoulias 哦,你说得对,已经修复了。是从LINQPad中错误地复制和粘贴的。 - Ondrej Tucny
@TheodorZoulias 关于您建议“删除”句子“编译器看不到GetEnumerator方法的通用版本,因为您是显式实现它”的建议 - 不,我不认为这会在任何方面“改进”我的问题。原因是,这是一个“事实”。在OP的情况下,编译器可以看到正好一个公共GetEnumerator方法,即public IEnumerator GetEnumerator(),并且解析算法在那一点停止。没有接口被检查。 - Ondrej Tucny
@TheodorZoulias 不,从我的回答中不能得出“如果您显式实现GetEnumerator,编译器将永远无法看到它”的结论。从A ⇒ B,您不能推断B ⇒ A。我的回答涵盖了OP的问题,我没有打算将两页规范复制粘贴到其中。请让我们结束这个讨论。 - Ondrej Tucny
显示剩余6条评论

0

我会尝试显式实现接口IEnumerable,并公开IEnumerable<T>

public IEnumerator<Options> GetEnumerator()
{
    return options.GetEnumerator();
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}

因此,默认情况下是通用函数,非通用函数只能显式调用。


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