将IEnumerable <t>转换为IEnumerator <t>做

时间:2017-03-01 18:31:38

标签: c# .net linq resharper ienumerable

我正在培训初学者一些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() - 方法调用枚举器,输出为:

  

元素数量:0

奇怪的是,如果更改类的构造函数,以便它为私有字段使用IList<T>

public SomeEnumerable()
{
    _numbers = Enumerable.Range(0,10).ToArray();
}

代码抛出正确的InvalidCastException。

我知道Enumerable.Range()在其实现中使用yield语句,并且只要GetEnumerator方法实现,编译器就可以做一些魔术,所以也许这是一个提示。

因此我的问题是:

  1. 为什么演员合法,为什么没有警告? (编译时间/ Resharper)
  2. 为什么你可以IEnumerableIEnumerator而不是IList投降IEnumerator而不会收到警告(ReSharper)
  3. 为什么要通过Count / foreach / etc调用GetEnumerator。产生空结果?
  4. 如果有充分理由这样做,框架的哪一部分会使用它?
  5. 一些澄清: 我知道不应该这样做,我知道它永远不会以这种方式工作。但我感兴趣的是为什么代码导致IL编译和执行完全正常但产生如此奇怪的结果

3 个答案:

答案 0 :(得分:4)

  

为什么演员合法,为什么没有警告?

演员的整个就是你告诉编译器,“我知道你不认为这种类型实际上是另一种类型,但我知道的比你多,让我把它看作是这个其他类型,如果我错了就在运行时抛出异常。

  

为什么你可以在没有警告的情况下将IEnumerable投射到IEnumerator而不是IList到IEnumerator(ReSharper)

无法将任何let startDate = moment('2017-02-02 15:36:00').toDate();投射到IEnumerable<T>。这个特定对象恰好实现了两个接口。其他对象,例如你编写的IEnumerator<T>,只会实现其中一个,而像这样的强制转换将会失败(在运行时)。

  

为什么要通过Count / foreach / etc调用GetEnumerator。产生空结果?

该对象不期望您将其转换为其他类型并开始在其上调用方法。你违反了合同类型。如果您希望它正常运行,请致电SomeEnumerable,它会为您提供适当的调查员。实际上,由于您破坏了初始化其数据的方法,因此您将获得一个未正确初始化的枚举器。

  

如果有充分理由这样做,框架的哪一部分会使用它?

它只是一种类似于:

的类型
GetEnumerator

如果你愿意,你可以写一个这样的课。

答案 1 :(得分:2)

查看Enumerable.Range(0,10)返回的类型:

System.Linq.Enumerable+<RangeIterator>d__110

这种类型由编译器生成,是一个小型状态机,用于跟踪yield语句中的迭代。此yield语句由Enumerable.Range语句在内部执行。

跑步......

Enumerable.Range(0,10).GetType().GetInterfaces()

...返回

typeof(IEnumerable<Int32>) 
typeof(IEnumerable) 
typeof(IEnumerator<Int32>) 
typeof(IDisposable) 
typeof(IEnumerator) 

所以你有它:它实现了IEnumerable<Int32>IEnumerator<Int32>。这就是演员成功的原因。转换始终会转换对象的 acutal 类型,而不是编译时类型。 _number的编译时类型为IEnumerable<int>,但它的实际类型仍然是生成的类型。

知道这一点很明显,为什么创建数组会导致无效的强制转换异常:它没有实现IEnumerator<int>

那么为什么new SomeEnumerable()会返回0个元素?我必须在这里推测一下,但我认为这是因为状态机在这里已经使用了两次,首先是枚举器,然后是可枚举的。在第二种用法中,它的内部指针已经在最后一次迭代。

答案 2 :(得分:1)

此代码编译没有问题

ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CONSTRAINT customers_pk PRIMARY KEY,
    first_name VARCHAR2(10) NOT NULL,
    last_na' at line 2

此代码编译也没有问题

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();
    }
}

所以你的问题似乎是Enumerable.Range返回一个实现IEnumerable和IEnumerator的对象。