2017-03-01 73 views
3

我正在培训初学者的一些核心概念的过程中的C#。在创建一个IEnumerable的随机数字在他的代码中的错误提示投IEnumerable<T>IEnumerator<T>什么演员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 - 方法编译完全正常。当初学者误将私人领域“_数字”ReSharper甚至建议添加我包括的显式演员。

现在考虑下面的测试代码:

var someEnumerable = new SomeEnumerable(); 
Console.WriteLine($"Number of elements: {someEnumerable.Count()}"); 

Count() - 方法调用枚举和输出是这样的:

Number of elements: 0

奇怪的是,如果你改变类的构造函数,以便它使用IList<T>作为私人领域

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

代码将引发适当的InvalidCastException。

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

因此,我的问题是:

  1. 为什么剧组法律和为什么没有一个警告? (编译时间/ ReSharper的)
  2. 为什么你可以施放IEnumerableIEnumerator但不是一个IListIEnumerator没有得到一个警告(ReSharper的)
  3. 为什么伯爵调用的GetEnumerator /的foreach /等。产生一个空的结果?
  4. 如果有很好的理由使这个工作,框架的哪个部分使用它?

一些澄清: 我知道这不应该这样做,我知道它不能这样工作。但我感兴趣的,为什么代码产生IL被编译并执行完美的罚款,但单产这种奇怪的结果

回答

4

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> 
{ 
    //... 
} 

你可以写一个类这样的,如果你想要的。

+0

这留下了一些未解答的问题。哪个类不指望我把它转换为另一种类型,为什么这个类会使用这两个接口? –

+1

@JanWolf您正在投射到'IEnumerator '的类。如果你想知道为什么设计课程的人按照他们的方式设计的,你可以问他们。我们只能猜测他们为什么选择按照他们的方式来设计课堂。 – Servy

+0

我会对细节感兴趣。我知道如何正确地做,这不是问题,我知道它不应该工作,这不是问题。问题是为什么它工作(为什么这个类实现两个接口),结果的枚举器做什么(什么是正在返回的枚举器) 我知道这是不正确的代码 –

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>。这就是演员成功的原因。投射始终投射实物类型的一个对象,而不是它的编译时间类型。编译时类型_numberIEnumerable<int>,但它的实际类型仍然是生成的类型。

知道了,明白为什么创建数组会导致无效的转换异常:它不会执行IEnumerator<int>

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

+0

谢谢。关于状态机的提示+查看反编译的代码并应用你所说的话似乎是非常确定的。 –

1

此代码编译没有问题

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的对象。

+1

这是很多的话来说明我已经得出的结论和Servy表示说:“这个特定的对象恰好实现了两个接口”。 –