将 IEnumerable<T> 转换为 IEnumerator<T> 有什么作用
What does casting an IEnumerable<T> to IEnumerator<T> do
我正在培训初学者一些 C# 的核心概念。在为随机数创建 IEnumerable 时,他的代码中的一个错误建议将 IEnumerable<T>
转换为 IEnumerator<T>
我知道这些接口都没有相互实现,所以让我印象深刻的是 ReSharper、编译器和运行时都没有抛出异常。
这是一些示例代码:
public class SomeEnumerable : IEnumerable<int>
{
private readonly IEnumerable<int> _numbers;
public SomeEnumerable()
{
_numbers = Enumerable.Range(0,10);
}
public IEnumerator<int> GetEnumerator()
{
return (IEnumerator<int>) _numbers;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
出乎意料的是,GetEnumerator
-方法编译得非常好。当初学者错误地输入私有字段“_numbers”时,ReSharper 甚至建议添加我已经包含的显式转换。
现在考虑下面的测试代码:
var someEnumerable = new SomeEnumerable();
Console.WriteLine($"Number of elements: {someEnumerable.Count()}");
Count()
-方法调用枚举器,输出是这样的:
Number of elements: 0
奇怪的是,如果您更改 class 的构造函数,以便它使用 IList<T>
作为私有字段
public SomeEnumerable()
{
_numbers = Enumerable.Range(0,10).ToArray();
}
代码抛出正确的 InvalidCastException。
我知道 Enumerable.Range()
在其实现中使用了 yield
语句,并且编译器可以在 GetEnumerator
方法被实现后立即施展魔法,所以也许就是这样一个提示。
因此我的问题是:
- 为什么转换是合法的,为什么没有警告? (编译time/Resharper)
- 为什么可以在没有收到警告的情况下将
IEnumerable
转换为 IEnumerator
而不能将 IList
转换为 IEnumerator
(ReSharper)
- 为什么 Count/foreach/etc 调用 GetEnumerator。产生空结果?
- 如果有充分的理由让它起作用,框架的哪一部分利用了它?
一些说明:
我知道不应该这样做,我也知道这样永远行不通。但我感兴趣的是,为什么代码生成的 IL 编译和执行都很好,但会产生如此奇怪的结果
Why is the cast legal and why isn't there a warning?
转换的全部 要点 是你告诉编译器,“我知道你不认为这种类型实际上是另一种类型,但我知道的比你多,让我把它当作另一种类型来对待,如果我错了就在运行时抛出异常。
Why can you cast an IEnumerable to an IEnumerator but not an IList to an IEnumerator without getting a warning (ReSharper)
您不能将任何 IEnumerable<T>
转换为 IEnumerator<T>
。这个特定对象恰好实现了这两个接口。其他对象,例如您编写的 SomeEnumerable
,将只实现一个或另一个,并且像这样的转换将失败(在运行时)。
Why does invoking GetEnumerator by Count/foreach/etc. yield an empty result?
该对象不希望您将其转换为另一种类型并开始对其调用方法。你违反了类型的合同。如果您希望它正常运行,请调用 GetEnumerator
,它会给您一个合适的枚举器。实际上,您得到的枚举器尚未正确初始化,因为您破坏了初始化其数据的方法。
If there is a good reason for this to work, which part of the framework makes use of that?
它只是一个看起来像这样的类型:
public class SomeClass: IEnumerable<int>, IEnumerator<int>
{
//...
}
如果你愿意,你可以这样写class。
查看由 Enumerable.Range(0,10)
编辑的 return 类型:
System.Linq.Enumerable+<RangeIterator>d__110
此类型由编译器生成,是一个小型状态机,用于跟踪 yield
语句期间的迭代。此 yield
语句由 Enumerable.Range
语句在内部执行。
运行 ...
Enumerable.Range(0,10).GetType().GetInterfaces()
...returns
typeof(IEnumerable<Int32>)
typeof(IEnumerable)
typeof(IEnumerator<Int32>)
typeof(IDisposable)
typeof(IEnumerator)
所以你已经明白了:它实现了 IEnumerable<Int32>
和 IEnumerator<Int32>
。这就是演员成功的原因。转换总是转换对象的 acutal 类型,而不是它的 compile-time 类型。 _number
的编译时类型是 IEnumerable<int>
,但它的实际类型仍然是生成的类型。
知道了,为什么创建数组会导致无效转换异常就很清楚了:它没有实现 IEnumerator<int>
.
那么为什么 new SomeEnumerable()
return 0
元素呢?我不得不在这里推测一下,但我认为这是因为状态机在这里被使用了两次,第一次作为枚举器,然后作为可枚举器。在第二次使用中,它的内部指针已经在最后一次迭代中。
这段代码编译没有问题
using System;
using System.Collections.Generic;
public class C {
public void M() {
IEnumerable<double> x = null;
var y = (IEnumerator<double>)x;
}
}
这段代码编译也没有问题
public class C {
public void M() {
IMagic1<double> x = null;
var y = (IMagic2<int>)x;
}
}
public interface IMagic1<T> {
IMagic2<T> Magic();
}
public interface IMagic2<T> {
void Magic2();
}
因为你通过强制转换告诉编译器你知道得更多,所以两者都能编译。
这会产生运行时错误:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace xyz
{
public class C {
public void M() {
IMagic1<double> x = new X1();
var y = (IMagic2<int>)x;
}
}
public class X1 : IMagic1<double> {
public IMagic2<double> Magic() {
return new X2();
}
}
public class X2 : IMagic2<double> {
public void Magic2() {
}
}
public interface IMagic1<T> {
IMagic2<T> Magic();
}
public interface IMagic2<T> {
void Magic2();
}
public class Program
{
public static void Main(string[] args)
{
new C().M();
}
}
}
这会产生运行时错误:
public class Program
{
public static void Main(string[] args)
{
var x = (IEnumerator<double>)(new double[] {0}).AsEnumerable();
}
}
这不是:
public class Program
{
public static void Main(string[] args)
{
var x = (IEnumerator<int>)Enumerable.Range(10,1);
}
}
所以你的问题似乎是 Enumerable.Range 返回一个实现了 IEnumerable 和 IEnumerator 的对象。
我正在培训初学者一些 C# 的核心概念。在为随机数创建 IEnumerable 时,他的代码中的一个错误建议将 IEnumerable<T>
转换为 IEnumerator<T>
我知道这些接口都没有相互实现,所以让我印象深刻的是 ReSharper、编译器和运行时都没有抛出异常。
这是一些示例代码:
public class SomeEnumerable : IEnumerable<int>
{
private readonly IEnumerable<int> _numbers;
public SomeEnumerable()
{
_numbers = Enumerable.Range(0,10);
}
public IEnumerator<int> GetEnumerator()
{
return (IEnumerator<int>) _numbers;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
出乎意料的是,GetEnumerator
-方法编译得非常好。当初学者错误地输入私有字段“_numbers”时,ReSharper 甚至建议添加我已经包含的显式转换。
现在考虑下面的测试代码:
var someEnumerable = new SomeEnumerable();
Console.WriteLine($"Number of elements: {someEnumerable.Count()}");
Count()
-方法调用枚举器,输出是这样的:
Number of elements: 0
奇怪的是,如果您更改 class 的构造函数,以便它使用 IList<T>
作为私有字段
public SomeEnumerable()
{
_numbers = Enumerable.Range(0,10).ToArray();
}
代码抛出正确的 InvalidCastException。
我知道 Enumerable.Range()
在其实现中使用了 yield
语句,并且编译器可以在 GetEnumerator
方法被实现后立即施展魔法,所以也许就是这样一个提示。
因此我的问题是:
- 为什么转换是合法的,为什么没有警告? (编译time/Resharper)
- 为什么可以在没有收到警告的情况下将
IEnumerable
转换为IEnumerator
而不能将IList
转换为IEnumerator
(ReSharper) - 为什么 Count/foreach/etc 调用 GetEnumerator。产生空结果?
- 如果有充分的理由让它起作用,框架的哪一部分利用了它?
一些说明: 我知道不应该这样做,我也知道这样永远行不通。但我感兴趣的是,为什么代码生成的 IL 编译和执行都很好,但会产生如此奇怪的结果
Why is the cast legal and why isn't there a warning?
转换的全部 要点 是你告诉编译器,“我知道你不认为这种类型实际上是另一种类型,但我知道的比你多,让我把它当作另一种类型来对待,如果我错了就在运行时抛出异常。
Why can you cast an IEnumerable to an IEnumerator but not an IList to an IEnumerator without getting a warning (ReSharper)
您不能将任何 IEnumerable<T>
转换为 IEnumerator<T>
。这个特定对象恰好实现了这两个接口。其他对象,例如您编写的 SomeEnumerable
,将只实现一个或另一个,并且像这样的转换将失败(在运行时)。
Why does invoking GetEnumerator by Count/foreach/etc. yield an empty result?
该对象不希望您将其转换为另一种类型并开始对其调用方法。你违反了类型的合同。如果您希望它正常运行,请调用 GetEnumerator
,它会给您一个合适的枚举器。实际上,您得到的枚举器尚未正确初始化,因为您破坏了初始化其数据的方法。
If there is a good reason for this to work, which part of the framework makes use of that?
它只是一个看起来像这样的类型:
public class SomeClass: IEnumerable<int>, IEnumerator<int>
{
//...
}
如果你愿意,你可以这样写class。
查看由 Enumerable.Range(0,10)
编辑的 return 类型:
System.Linq.Enumerable+<RangeIterator>d__110
此类型由编译器生成,是一个小型状态机,用于跟踪 yield
语句期间的迭代。此 yield
语句由 Enumerable.Range
语句在内部执行。
运行 ...
Enumerable.Range(0,10).GetType().GetInterfaces()
...returns
typeof(IEnumerable<Int32>)
typeof(IEnumerable)
typeof(IEnumerator<Int32>)
typeof(IDisposable)
typeof(IEnumerator)
所以你已经明白了:它实现了 IEnumerable<Int32>
和 IEnumerator<Int32>
。这就是演员成功的原因。转换总是转换对象的 acutal 类型,而不是它的 compile-time 类型。 _number
的编译时类型是 IEnumerable<int>
,但它的实际类型仍然是生成的类型。
知道了,为什么创建数组会导致无效转换异常就很清楚了:它没有实现 IEnumerator<int>
.
那么为什么 new SomeEnumerable()
return 0
元素呢?我不得不在这里推测一下,但我认为这是因为状态机在这里被使用了两次,第一次作为枚举器,然后作为可枚举器。在第二次使用中,它的内部指针已经在最后一次迭代中。
这段代码编译没有问题
using System;
using System.Collections.Generic;
public class C {
public void M() {
IEnumerable<double> x = null;
var y = (IEnumerator<double>)x;
}
}
这段代码编译也没有问题
public class C {
public void M() {
IMagic1<double> x = null;
var y = (IMagic2<int>)x;
}
}
public interface IMagic1<T> {
IMagic2<T> Magic();
}
public interface IMagic2<T> {
void Magic2();
}
因为你通过强制转换告诉编译器你知道得更多,所以两者都能编译。
这会产生运行时错误:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace xyz
{
public class C {
public void M() {
IMagic1<double> x = new X1();
var y = (IMagic2<int>)x;
}
}
public class X1 : IMagic1<double> {
public IMagic2<double> Magic() {
return new X2();
}
}
public class X2 : IMagic2<double> {
public void Magic2() {
}
}
public interface IMagic1<T> {
IMagic2<T> Magic();
}
public interface IMagic2<T> {
void Magic2();
}
public class Program
{
public static void Main(string[] args)
{
new C().M();
}
}
}
这会产生运行时错误:
public class Program
{
public static void Main(string[] args)
{
var x = (IEnumerator<double>)(new double[] {0}).AsEnumerable();
}
}
这不是:
public class Program
{
public static void Main(string[] args)
{
var x = (IEnumerator<int>)Enumerable.Range(10,1);
}
}
所以你的问题似乎是 Enumerable.Range 返回一个实现了 IEnumerable 和 IEnumerator 的对象。