2017-03-01 82 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表示說:「這個特定的對象恰好實現了兩個接口」。 –