2010-09-09 13 views
19
static void Main(string[] args) 
    { 
     List<int> listArray = new List<int>(); 
     listArray.Add(100); 
     foreach (int item in listArray) 
      Console.WriteLine(item); 
    } 

a) Cuando foreach declaración pide listArray's IEnumerable<int>.GetEnumerator() aplicación, no se llaman a través de listArray.GetEnumerator() o IEnumerable<int>.GetEnumerator() o IEnumerable.GetEnumerator()?¿Cómo llama foreach a GetEnumerator()? ¿A través de la referencia de IEnumerable o vía ...?

b) Del mismo modo, cuando foreach referencias objeto devuelto por listArray's IEnumerable<int>.GetEnumerator(), ¿se Referencia este objeto a través de IEnumerator o IEnumerator<int> tipo de referencia?

gracias

EDIT:

Algunos de mis preguntas van a citar el texto:

o realizar la búsqueda de miembro del tipo X con GetEnumerator identificador y no hay argumentos de tipo . Si la búsqueda de miembro no produce una coincidencia, o produce una ambigüedad, o produce una coincidencia que no es un grupo de métodos, compruebe si hay una interfaz enumerable como que se describe a continuación. Se recomienda que se emita una advertencia si la búsqueda del miembro produce algo excepto un grupo de métodos o no coincide.

o Realice la resolución de sobrecarga usando el grupo de métodos resultante y una lista de argumentos vacíos . Si la sobrecarga de resultados resolución en ningún métodos aplicables, resulta en una ambigüedad, o resultados en un único mejor método pero que el método es o bien estática o no público, comprobar para una interfaz enumerable como se describe a continuación. Es recomendado que se emita una advertencia si la resolución de sobrecarga produce cualquier cosa excepto un método de instancia público inequívoco o ningún método aplicable .

o Si el tipo de retorno del método E GetEnumerator no es una clase, estructura o tipo de interfaz, un error es producido y no hay nuevas medidas son tomadas .

o La búsqueda de miembros se realiza en E con el identificador Current y no escribe argumentos. Si la búsqueda de miembro no produce coincidencias, el resultado es un error , o el resultado es cualquier cosa excepto una propiedad de instancia pública que permite leer, se produce un error y no se realizan más pasos.

o La búsqueda de miembros se realiza en E con con el identificador MoveNext y sin argumentos de tipo . Si la búsqueda de miembro no produce coincidencias, el resultado es un error , o el resultado es cualquier cosa excepto un grupo de métodos, se produce un error y no se realizan más pasos .

o La resolución de sobrecarga se realiza en el grupo de métodos con una lista de argumentos vacía. Si la resolución de la sobrecarga que no resulta en los métodos aplicables, resultados en una ambigüedad, o resultados en un mejor método, pero ese método es estática o no pública, o su tipo retorno no es BOOL, un error es producido y no hay más pasos son tomado.

o El tipo de colección es X, el tipo empadronador es E, y el tipo de elemento es el tipo de la propiedad Current .

  • De lo contrario, compruebe si hay una interfaz enumerables: o Si hay exactamente un tipo T de tal manera que hay una implícita la conversión de X a la interfaz System.Collections.Generic.IEnumerable, entonces la colección tipo es esta interfaz , el tipo empadronador es la interfaz System.Collections.Generic.IEnumerator, y el tipo de elemento es T.

  • de lo contrario, si hay más de un tipo tal T, entonces se produce un error y no se toman más pasos .

  • De lo contrario, si hay una conversión implícita de X a la interfaz System.Collections.IEnumerable , entonces el tipo de recogida es esta interfaz, el tipo empadronador es la interfaz System.Collections.IEnumerator, y el tipo de elemento es objeto.

  • De lo contrario, se produce un error y no se realizan más pasos .

1)

Presupuesto de Eric Lippert:

Opción (1) es correcta. Tenga en cuenta que este significa que el enumerador devuelto es una estructura mutable sin caja.

El hecho de que esta es una estructura mutable tiene efectos muy reales si lo hace algo tonto como pasar alrededor la estructura como si se tratara de un tipo de referencia; se copiará por el valor , no por referencia.

De http://en.csharp-online.net/ECMA-334:_15.8.4_The_foreach_statement:

foreach (V v en x)-instrucción incrustada

continuación se expande a:

{ 
    E e = ((C)(x)).GetEnumerator(); 
    try { 
     V v; 
     while (e.MoveNext()) { 
     v = (V)(T)e.Current; 
     embedded-statement 
     } 
    } 
    finally { 
     … // Dispose e 
    } 
} 

La variable e no es visible para o accesible para la expresión x o la declaración incrustada o cualquier otra fuente código del programa.

En caso de listArray, el empadronador devuelto se guarda (es decir, su valor se guarda) en la variable e (por lo tanto variable de e es un struct mutable) .Pero de acuerdo con el extracto anterior, e no es accesible a mi fuente código, entonces, ¿cómo podría pasar esta estructura (a menos que escriba el código que hace manualmente lo que foreach declaración hace automáticamente)?

2)

de búsqueda Miembro se realiza en E con el identificador actual y no hay argumentos de tipo. Si la búsqueda de miembro no produce coincidencias, el resultado es un error, o el resultado es cualquier cosa excepto una propiedad de instancia pública que permite la lectura, se produce un error y no se toman pasos adicionales.

Parece que si ponemos en práctica GetEnumerator en la clase (X) en sí, entonces Current debería también ser implementado en la clase (E) sí (por lo tanto E no debe poner en práctica de manera explícita Current), ya que el compilador no lo hará molestarse en verificar las interfaces IEnumerator<T>/IEnumerator en los casos en que la búsqueda de miembros (en E con el identificador Current) no produce una coincidencia?

3)

Si hay exactamente un tipo T tal que hay una conversión implícita de X a la System.Collections.Generic.IEnumerable interfaz, entonces el tipo de colección es esta interfaz , la tipo empadronador es la System.Collections.Generic.IEnumerator interfaz , y el tipo de elemento es T.

de acuerdo con lo anterior, si foreach que verificar para IEnumerable<T> interfaz, entonces foreach siempre usará IEnumerator<T> versión de Current? Por lo tanto, si se implementa de forma explícita EIEnumerator<T> versión de Current y si se implementa también otra versión de Current en la propia clase, el foreach siempre llamar la versión de IEnumerable<T>Current?

4)

El método GetEnumerator se documenta como entrega de uno de estos:

http://msdn.microsoft.com/en-us/library/x854yt9s.aspx

¿Qué quiere decir por uno de estos (como en plural)? El enlace que proporcionó dice GetEnumerator (implementado por List<T>) solo devuelve struct tipo.

5)

g. El tipo de colección es X, el tipo enumerador es E, y el tipo de elemento es el tipo de la propiedad actual

Quizás una pregunta inútil - acuerdo con lo anterior, foreach no comprueba qué tipo de elementos de algunos por el usuario colección definida realmente almacena, pero en su lugar asume que el tipo de elementos es el mismo que el tipo devuelto por la propiedad Current?

+0

¿Has considerado escribir una prueba simple para descubrirlo? Sería bastante trivial escribir una clase que implemente 'IEnumerable.GetEnumerator()' y GetEnumerator() y que devuelva diferentes enumeradores. – Aren

+0

He actualizado mi respuesta para abordar sus preguntas adicionales. –

Respuesta

15

(a) Cuando la instrucción foreach llama a la implementación IEnumerable.GetEnumerator() de listArray, ¿la llama a través de (1) listArray.GetEnumerator() o (2) IEnumerable.GetEnumerator() o (3) IEnumerable.GetEnumerator() ?

La opción (1) es correcta. Tenga en cuenta que esto significa que el enumerador devuelto es una estructura mutable unboxed. El método GetEnumerator se documenta como entrega de uno de estos:

http://msdn.microsoft.com/en-us/library/x854yt9s.aspx

El hecho de que esta es una estructura mutable tiene efectos muy reales si lo hace algo tonto como pasar alrededor de la estructura, como si se tratara de un tipo de referencia; se copiará por valor, no por referencia.

(1) Sin embargo, de acuerdo con el extracto anterior, e no es accesible a mi código fuente, así que ¿cómo iba a ser capaz de pasar esta struct alrededor (a menos que escribo código que hace manualmente lo instrucción foreach lo hace automáticamente) ?

Usted está correcto. No estaba claro. Mi punto era que si escribes código que hace lo que foreach hace y te metes con el objeto enumerador tú mismo, entonces debes tener cuidado. El equipo de CLR se dio cuenta de que la gran mayoría de las personas estaría utilizando el ciclo foreach y, por lo tanto, no estaría expuesto al riesgo de utilizar incorrectamente el enumerador.

(2) Parece que si ponemos en práctica GetEnumerator en la clase X, y, a continuación, actual debe también ser implementado en la propia clase E ya que el compilador no se molestan en comprobar si los elementos de interfaz explícitos en los casos en búsqueda de miembros no produce una coincidencia?

Correcto.

(3) si foreach tiene que comprobar si hay IEnumerable<T> interfaz, foreach se utilice siempre la versión actual de IEnumerator<T>? Por lo tanto, si E implementa explícitamente la versión IEnumerator<T> de Current y si también implementa otra versión de Current en la misma clase, el foreach siempre llamará a IEnumerable<T> versión de Current?

Correcto. Si llega al punto en el que estamos buscando en la interfaz, entonces vamos a usar la interfaz.

(4) ¿Qué quiere decir con "uno de ellos"

que quería decir que le proporcione una instancia de la estructura.

(5) de acuerdo con lo anterior, foreach no comprueba qué tipo de elementos realmente almacena una colección definida por el usuario, sino que asume que el tipo de elementos es el mismo que el tipo devuelto por la propiedad Actual.

Correcto. Verifica que el elenco tenga éxito. Por ejemplo, si usted dice

foreach(int x in myObjects) 

donde myObjects le da un enumerador cuya corriente es del tipo de objeto, entonces el bucle supone que cada objeto puede ser lanzado con éxito a int, y produce una excepción en tiempo de ejecución, si eso es incorrecto . Pero si usted dice parecida:

foreach(string x in myInts) 

entonces el compilador en cuenta que si devuelve un int actual entonces la colección no contiene una cadena, y fallará a compilar el programa.

(b) De manera similar, cuando las referencias foreach objeto devuelto por IEnumerable.GetEnumerator de ListArray(), lo hace referencia este objeto a través de IEnumerator o IEnumerator tipo de referencia?

La pregunta se basa en la respuesta a la primera pregunta que es la opción (2).Dado que la pregunta se basa en una falsedad, no puede responderse con sensatez.

+0

¿Podrían ver mi publicación editada? – user437291

+0

gracias a todos por su ayuda – user437291

13

El comportamiento de foreach se detalla en la especificación del lenguaje, sección 8.8.4. En pocas palabras

foreach (T t en la expresión)

  • Si expresión es una matriz *, utilizar el interfaz IEnumerable (* ver el comentario de Eric Lippert a continuación.)
  • Porque si expresión tiene método GetEnumerator, el uso que
  • Else si expresión es convertible a IEnumerable<T>, utilice esa interfaz y IEnumerator<T> (y métodos asociados)
  • Porque si expresión se puede convertir en IEnumerable, usar esa interfaz y IEnumerator (y métodos asociados)

Y hay varias condiciones de error y cosas que estoy pasando por alto. Pero, en resumen, si su colección es genérica, será para las opciones de interfaz genéricas.

+10

Tenga en cuenta que el primer punto es potencialmente engañoso. El código generado por el compilador C# no * realmente * usa la interfaz IE al enumerar una matriz (o cadena) con foreach. Más bien, genera el mismo bucle "for (int i = 0; i * para fines de análisis de tipo * pero en realidad * no genera el código * de esa manera. –

+1

@Eric, ¡eso es interesante! –

6

Desde el lenguaje de especificación C# 3.0 (Sec 8.8.4.):

El procesamiento en tiempo de compilación de una instrucción foreach determina en primer lugar el tipo de colección, tipo enumerador y el elemento de tipo de la expresión. Esta determinación procede como sigue:

  1. Si el tipo X de la expresión es de tipo una matriz entonces hay una conversión de referencia implícita de X a la interfaz System.Collections.IEnumerable (desde System.Array implementa esta interfaz). El tipo de recopilación es la interfaz System.Collections.IEnumerable, el tipo de enumerador es la interfaz System.Collections.IEnumerator y el tipo de elemento es el tipo de elemento de la matriz tipo X.
  2. De lo contrario, determinar si el tipo X tiene un método GetEnumerator apropiado:

    a. Realice la búsqueda de miembros en el tipo X con el identificador GetEnumerator y sin argumentos de tipo. Si la búsqueda de miembro no produce una coincidencia, o produce una ambigüedad, o produce una coincidencia que no es un grupo de métodos, verifique si hay una interfaz enumerable como se describe a continuación. Se recomienda que se emita una advertencia si la búsqueda de miembros produce algo excepto un grupo de métodos o ninguna coincidencia.

    b. Realice una resolución de sobrecarga utilizando el grupo de métodos resultante y una lista de argumentos vacía. Si la resolución de sobrecarga no da lugar a métodos aplicables, genera una ambigüedad o da como resultado un único mejor método, pero dicho método es estático o no público, busque una interfaz enumerable como se describe a continuación. Se recomienda que se emita una advertencia si la resolución de sobrecarga produce algo, excepto un método de instancia público inequívoco o ningún método aplicable.

    c. Si el tipo de retorno E del método GetEnumerator no es una clase, estructura o tipo de interfaz, se produce un error y no se realizan más pasos.

    d. La búsqueda de miembros se realiza en E con el identificador Actual y sin argumentos de tipo. Si la búsqueda de miembros no produce coincidencias, el resultado es un error o el resultado es cualquier cosa, excepto una propiedad de instancia pública que permite la lectura, se produce un error y no se realizan más pasos.

    e. La búsqueda de miembros se realiza en E con el identificador MoveNext y sin argumentos de tipo. Si la búsqueda de miembros no produce coincidencias, el resultado es un error o el resultado es cualquier cosa, excepto un grupo de métodos, se produce un error y no se realizan más pasos.

    f. La resolución de sobrecarga se realiza en el grupo de métodos con una lista de argumentos vacía. Si la resolución de sobrecarga no da lugar a métodos aplicables, genera una ambigüedad o resulta en un único mejor método, pero ese método es estático o no público, o su tipo de devolución no es bool, se produce un error y no se realizan más pasos.

    g. El tipo de recopilación es X, el tipo de enumerador es E y el tipo de elemento es el tipo de la propiedad Actual.

En resumen, el compilador actúa como si el foreach fuera el siguiente código, realizar llamadas polimórficas y mirando a la definición de interfaz enumerables definido (si lo hay) para determinar los tipos y métodos adecuados:

var iterator = listArray.GetEnumerator(); 
while(iterator.MoveNext()) 
{ 
    var item = iterator.Current; 
    Console.WriteLine(item); 
} 
+3

Además, por supuesto, el código para deshacerse del enumerador. Además, tenga en cuenta que este no es el código generado si la colección es una matriz o cadena. –

Cuestiones relacionadas