2010-10-12 9 views
5

¿Qué hace el siguiente código?foreach (Obj derivado en la nueva lista <Base>())

class Base { } 
class Derived : Base { } 
class Test 
{ 
    void Foo(List<Base> list) 
    { 
     foreach (Derived obj in list) 
     { 
      // ... 
     } 
    } 
} 

No esperaba que incluso compilara, pero lo hace.

+0

Lea sobre el polimorfismo, aquí hay un lugar para comenzar: http://en.wikipedia.org/wiki/Polymorphism_in_object-oriented_programming – Donnie

+4

Sé lo que es el polimorfismo. –

Respuesta

10

El comportamiento que está observando está de acuerdo con la sección 8.8.4 La declaración forecech de la especificación del lenguaje C#. Esta sección define la semántica de la declaración foreach de la siguiente manera:

[...] Los pasos anteriores, si tiene éxito, sin ambigüedades producir un tipo de colección C, empadronador tipo E y elemento de tipo T. Un foreach enunciado de la forma

foreach (V v in x) embedded-statement 

A continuación se expande a:

{ 
    E e = ((C)(x)).GetEnumerator(); 
    try { 
     V v; 
     while (e.MoveNext()) { 
      // here the current item will be casted     
      v = (V)(T)e.Current; 
      embedded-statement 
     } 
    } 
    finally { 
     // Dispose e 
    } 
} 

La razón por la que los insertos del compilador esta conversión explícita es histórico. C# 1.0 no tenían tan genéricos con el fin de permitir que el código simple como esto

ArrayList list = new ArrayList(); 
list.Add(1); 
foreach (int i in list) 
{ 
    ... 
} 

se decidió dejar que el compilador introducir el elenco.

7

Absolutamente nada, pero lo hace de una manera muy ineficiente.

El orden de las operaciones es la siguiente:

  1. instanciar una nueva lista, con cero elementos
  2. iterar sobre esa lista recién instanciado, que tiene cero artículos
  3. No convertir el objeto base a un Derivado objeto, porque la lista tiene cero elementos. Si la lista tuviera algún elemento, este paso daría lugar a una excepción en tiempo de ejecución.
  4. No ejecuta el código en el bloque foreach, porque la lista tiene cero elementos.

Editar
Sobre la base de su edición, se corre el riesgo de una InvalidCastException si cualquier elemento de la lista que se pasa a Foo no ser en realidad un objeto Derived.

Edit2
¿Por qué compila? Debido foreach implica una conversión implícita a Object para cada elemento de la lista, y luego otra conversión explícita al tipo especificado en el bloque foreach, en este caso Derived

+2

Derecha, gracias. Cambie su respuesta para que diga algo como "Foreach intenta silenciar silenciosamente todos los elementos a Derivados y tira cuando eso es imposible". - Entonces responderá mi pregunta exactamente, y podré aceptar. –

+0

@Stefan Monov: te gané por eso, creo que 3 segundos. – Randolpho

+1

No creo que su "Edit2" sea correcto. No hay un molde implícito para objetar, hay moldes explícitos involucrados. Primero al tipo definido en la colección (en este caso, Base), luego al tipo definido en el foreach. –

0

No sé por qué no esperar que esto compila. Considere esto, que es funcionalmente equivalente:

{ 
    List<Base> list = new List<Base>(); // creates new, empty list of Base 
    foreach (Derived obj in list) 
    { 
    // ... 
    } 
} 

¿Eso deja más claro lo que está sucediendo en su código?

EDITAR versión revisada.

Ahora lanzará InvalidCastException si su lista contiene algo que no es una instancia de Derived. Derivado 'es una' Base así que no hay problema con compilar esto.

+1

No espero que se compile porque normalmente no se puede asignar un objeto Base a una variable derivada sin un downcast explícito, como en 'Derived d = new Base() ' –

+0

@Stefan - vea la respuesta de @Jon Hanna –

0

Es equivalente a:

Enumerator<Base> enumerator = new List<Base>().GetEnumerator(); 
while (enumerator.MoveNext()) 
{ 
    Derived obj = (Derived) enumerator.Current; 
    // ... 
} 

medida que su lista está vacía, el bloque interno no se ejecuta.

Si fuera para contener elementos, entonces existe el riesgo de InvalidCastException en caso de que alguno de los elementos no sea del tipo Derived.

+1

Si quiere ser más preciso, en realidad hay un reparto doble en marcha. 'Derivado Obj = (Derivado) (Base) enumerador.Current;' –

+1

Quiero invitarlo, pero no puedo. foreach no usa enumeradores genéricos. – Randolpho

+0

@Randolpho, C# especificación 8.8.4: "Si hay exactamente un tipo T tal que hay una conversión implícita de X a la interfaz System.Collections.Generic.IEnumerable , entonces el tipo de colección es esta interfaz, el tipo de enumerador es la interfaz System.Collections.Generic.IEnumerator , y el tipo de elemento es T. " –

0

Técnicamente, Randolpho es correcto: su código no hace nada. Pero creo que estás llegando a un punto diferente.

casting los elementos de lista a Derived le dará acceso a las propiedades definidas en la clase Derived además de las propiedades definidas en la clase Base. Sin embargo, obtendrá errores si tiene elementos que no son del tipo Derived en la lista cuando accede a esas propiedades.

Salvo alguna otra necesidad, es mejor tener la lista definida como List<Derived>.

3

foreach incluye un elenco. Considere que se puede usar con enumeraciones de objetos sin plantillas. Casting de Base a Derived es válido, por lo que el código es válido, pero podría lanzar una excepción en el tiempo de ejecución.

+0

Ah, bien, ahora he aprendido otra cosa: siempre he pensado que cuando uso una colección no genérica, necesito usar una variable "System.Object", como esta: 'foreach (object a in list)' . Ahora sé que me permite poner cualquier tipo en el lugar de 'object', allí. –

+0

Vale la pena recordar que los genéricos no existían en .NET hasta 2.0. Como tal, si bien tal vez tendría sentido hoy en día tener 'forecach' no involucrar un elenco para una tipificación más fuerte, esto habría sido una molestia en los días pre-genéricos cuando el hecho de que uno tenía que usar colecciones de objetos en tiempos donde uno Sabía que el tipo de todos los elementos habría dado lugar a una gran cantidad de fundición explícita. –

0

lo mismo que

list.Cast<Derived>(); 

simplemente la conversión de tipos. en algún caso, puede obtener el error

Cuestiones relacionadas