C # foreach comportamento inesperado

8

Por que o compilador C # permite que isso compile e lança uma exceção de tempo de execução quando executado?

class Program
{
   static void Main(string[] args)
   {
      IEnumerable<Test> list = new List<Test>() { new Test() };

      foreach(IDisposable item in list)
      {

      }
   }
}

public class Test
{

}

Isso compila com qualquer interface e não compila se você substituir IDisposable por classe concreta.

ekalchev
fonte
4
Ele é compilado porque podem existir instâncias derivadas das Testquais são implementadas IDisposable. Efetivamente, o foreach tenta converter todos os elementos na interface e lança uma exceção se isso falhar. O mesmo que se você escrevesse (IDisposable)currentElement. No entanto, não vejo por que não deveria compilar na classe concreta.
HimBromBeere
De acordo com o meu violino que compila muito bem: dotnetfiddle.net/3Up9nU
HimBromBeere
3
@HimBromBeere: Sim, esse é o ponto da questão.
Jon Skeet
você pode ter certeza que você não vai correr em uma exceção usando foreach (IDisposable item in list.OfType<IDisposable>()), mas eu acho que não é o ponto
Innat3

Respostas:

16

O foreachloop tem um elenco implícito. É mais ou menos assim:

using (IEnumerator<Test> iterator = list.GetEnumerator())
{
    while (iterator.MoveNext())
    {
        IDisposable item = (IDisposable) iterator.Current;
        // Body of foreach loop here
    }
}

Antes dos genéricos, isso era muito mais prático do que ter que converter o código-fonte. Agora não é tão importante, mas seria estranho não compilar. Note que o compilador irá verificar que ele é, pelo menos viável . Se você usar foreach (string item in list)isso não seria compilado, porque a Testnão pode ser um string- mas a Test pode ser um IDisposable, porque poderia se referir a uma instância de uma subclasse desses Testimplementos IDisposable. Se você tornar a Testclasse selada, ela também falhará na compilação IDisposable, porque uma Testinstância não poderá ser implementada IDisposable.

Basicamente, ele será compilado se uma conversão de Testpara o tipo de iteração for compilada e falhará ao compilar de outra forma. Mas falhará no tempo de execução se uma conversão normal também falhar no tempo de execução.

Jon Skeet
fonte