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.
Testquais são implementadasIDisposable. 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.