2012-02-07 10 views
8

Tengo una aplicación .NET que utiliza un conjunto (.dll) que define algún método:Por qué la adición de un tipo de retorno a un método de regresar vacío provoca una MissingMethodException

public void DoSomething() 
    { 
     // Do work 
    } 

Supongamos que este método de firma a los cambios incluyen una tipo cadena retorno:

public string DoSomething() 
    { 
     // Do work 
     return "something"; 
    } 

¿por qué el código que utiliza este método no funciona en un System.MissingMethodException?

Me parece que, en todos los sitios de llamadas a este método, no se utilizó el valor devuelto (ya que no existía antes).

¿Por qué este cambio rompe el código?

+0

Editado mi ejemplo. Mi punto era que el método devuelve algo ahora; pero todo el código que lo usó previamente no hace nada con eso. Es perfectamente legal escribir dicho código. –

Respuesta

1

Porque hiciste un cambio de API apresurada. Cambiaste la firma del método en una clase base o una interfaz.

Las personas que llaman están vinculadas por este método. En IL, una referencia de método no es solo una referencia a un tipo y su método con algún índice del método, sino que las referencias al método de las personas que llama incluyen la firma del método completo.

Este cambio se puede reparar compilando todos los ensamblajes que llaman a este método pero obtiene excepciones de tiempo de ejecución cuando solo recompila el ensamblado modificado y espera que los ensamblajes que utilizan recogerán mágicamente la firma de método modificada. Este no es el caso porque la referencia del método contiene la firma del método completo y el tipo de definición.

Me pasó a mí también. Tiene razón en que nadie podría usar el tipo de devolución, por lo que este cambio es seguro, pero debe volver a compilar todos los objetivos afectados.

+0

Gracias. Tenía la impresión de que solo el nombre del método + los tipos de parámetros se usaban para inferir qué método llamar. –

+1

es en realidad typeref + methodref donde la referencia del método contiene nombre, tipo de retorno, argumento del tipo, argumentos genéricos y convención de llamadas –

+0

@riortal: C# no utiliza el tipo de retorno para determinar qué método llamar. Pero C# definitivamente necesita saber el tipo de retorno * para generar el código correcto en la persona que llama del método *. –

0

Los fabricantes de C# decidieron que debería ser estricto sobre las firmas de métodos.

+1

De la excepción ('MissingMethodException') Creo que está describiendo un error de tiempo de ejecución causado por reemplazar el dll con otro. Si esto es así, entonces no tiene nada que ver con C#. –

2

Si no hay reflexión, pero el enlace es estático (supongo que por la descripción), el motor de ejecución intentará encontrar un método con la firma exacta. Así es como funciona el callvirt de CIL.

No importa si el valor se consume o no: el tiempo de ejecución no puede ubicar el void YourClass::DoSomething() y ni siquiera intenta buscar string YourClass::DoSomething().

Si fue posible dicho cambio, entonces podría explotar fácilmente el tiempo de ejecución al causar desbordamientos/desbordamientos de pila.

+0

+1 Sin mencionar que no le gustaría adivinar y quizás escoger el método incorrecto, lo que podría volverse muy desagradable ... – Basic

+0

Pensé que elegir el método solo se hacía al mirar su nombre + parámetro. –

+2

¡Has pensado mal! – adelphus

1

Porque ha cambiado la firma del método.

Cuando el código externo necesita localizar un método, necesita asegurarse de que lo que está llamando es correcto. Almacena parte de esta información como una firma en tiempo de compilación: la información de la firma incluye el tipo de devolución (independientemente de si se usa realmente en algún lugar).

En lo que respecta al CLR, ya no existe un método con un tipo de retorno de vacío, de ahí el MissingMethodException.

+0

Interesante. Me pregunto cómo se verá el MSIL (antes y después). –

14

Las otras respuestas que indican que ha cambiado la firma de un método y, por lo tanto, deben recompilar a las personas que llaman, son correctas.Pensé que podría agregar algo de información adicional sobre este bit de su pregunta:

Me parece que, en todos los sitios de llamadas a este método, no se utilizó el valor de retorno (ya que no existía antes)

Eso es exactamente correcto. Ahora, considere esta pregunta: ¿cómo se escribe el código que no hace uso de los datos? Parece que está trabajando bajo la suposición completamente falsa de que que no utiliza un valor no requiere código, pero que no use un valor ¡ciertamente requiere código!

Suponga que tiene métodos:

static int M1(int y) { return y + 1; } 
static void M2(int z) { ... } 

y usted tiene una llamada

int x; 
x = M1(123); 

¿Qué ocurre a nivel de IL? lo siguiente:

  • Asigne espacio en el grupo temporal para x.
  • Empuje 123 en la pila
  • Invoke M1.
  • Presione 1 en la pila. Stack ahora es 1, 123
  • Agregue las dos cosas principales en la pila. Esto aparece tanto y empuja el resultado. Stack es ahora 124.
  • volver a la llamada
  • La pila es todavía 124.
  • tienda el valor en la pila en el almacenamiento temporal de x. Esto hace estallar la pila, por lo que la pila ahora está vacía.

Supongamos ahora tienes que hacer:

M1(345); 

¿Qué ocurre? Lo mismo :

  • empuje 345 en la pila
  • invocación M1.
  • Presione 1 en la pila. Stack ahora es 1, 345
  • Agregue las dos cosas principales en la pila. Esto aparece tanto y empuja el resultado. Pila es ahora 346.
  • volver a la llamada
  • La pila es todavía 346.

Pero no hay instrucción que almacena el valor en la pila en cualquier lugar, así que tenemos que emitir una instrucción pop:

  • Muestra el valor no utilizado de la pila.

Ahora supongamos que llame

M2(456); 

¿Qué ocurre?

  • empuje 456 en la pila
  • invocación M2.
  • M2 hace su trabajo. Cuando vuelve a la persona que llama, la pila está vacía porque no se devuelve.
  • La pila ahora está vacía, así que no separe nada de ella.

¿Ahora ves por qué cambiar un método de vacío al regresar al valor devuelto es un cambio radical? Cada persona que llama ahora tiene que sacar el valor no utilizado de la pila. Al hacer nada con datos todavía es necesario limpiarlo de la pila. Estás desalineando la pila si no bajas ese valor; el CLR requiere que la pila sea vacía al comienzo de cada declaración para garantizar que este tipo de desalineación no ocurra.

+1

+100 Achievement Unlocked - (Eric Lippert respondió a tu pregunta) –

+0

Una pila balanceada es algo bueno. Pero a veces me gustaría copiar algunos valores de la pila de mi llamador, especialmente el manejador del método, para obtener un recorrido de pila muy barato de mi interlocutor. En el nivel de IL, no he encontrado ninguna forma de hacer esto. ¿Conoces esa posibilidad o debo recurrir a GetStackFramesInternal? –

+0

@AloisKraus la pila de evaluación de IL no es lo mismo que la pila de llamadas. La pila de evaluación es bastante más virtual. Vea la respuesta de Eric http://stackoverflow.com/a/7877736/385844 para más detalles. – phoog

Cuestiones relacionadas