2012-02-21 7 views
15

Inspirado por este question.¿Por qué una expresión de invocación de método tiene un tipo dinámico incluso cuando solo hay un posible tipo de retorno?

versión corta: ¿Por qué no la cifra compilador el tipo en tiempo de compilación de M(dynamic arg) si sólo hay una sobrecarga de M o la totalidad de las sobrecargas de M tienen el mismo tipo de retorno?

por la especificación, §7.6.5:

Una invocación-expresión se une de forma dinámica (§7.2.2) si al menos una de las siguientes bodegas:

  • La primaria -expression tiene tipo de tiempo de compilación dinámico.

  • Al menos un argumento de la lista de argumentos opcional tiene el tipo de tiempo de compilación dinámico y la expresión primaria no tiene un tipo de delegado.

Tiene sentido que para

class Foo { 
    public int M(string s) { return 0; } 
    public string M(int s) { return String.Empty; } 
} 

el compilador no puede averiguar el tipo en tiempo de compilación de

dynamic d = // dynamic 
var x = new Foo().M(d); 

porque no sabrá hasta el tiempo de ejecución, que se invoca sobrecarga de M.

Sin embargo, ¿por qué el compilador no puede deducir el tipo de tiempo de compilación si M tiene solo una sobrecarga o todas las sobrecargas de M devuelven el mismo tipo?

Estoy tratando de entender por qué la especificación no permite que el compilador escriba estas expresiones estáticamente en tiempo de compilación.

+0

Cualquiera que involucre dinámica es, por la especificación, dinámico ** completamente ** hasta que se convierte en algo más. La dinámica es infecciosa y se expande para hacer que todo lo que * utiliza * dinámico - ** se vuelva ** dinámico. –

+1

Sí, lo sé. La pregunta es * por qué * está la especificación escrita de esta manera, incluso cuando solo hay un tipo posible para la expresión. Aquí, estamos viendo el ejemplo de una expresión de invocación. – jason

+0

No creo que este sea el motivo, de hecho, es posible que nunca se haya tenido en cuenta durante el diseño, pero mantener el resultado "dinámico" evita el desempaquetado y el nuevo compartimiento si el resultado es un tipo de valor y el resto del método continúa que resultan en otras llamadas al método 'dinámico'. – hvd

Respuesta

22

ACTUALIZACIÓN: Esta pregunta fue the subject of my blog on the 22nd of October, 2012. Gracias por la gran pregunta!


Por qué no puede la figura compilador a cabo el tipo de tipo de compilación de M(dynamic_expression) si sólo hay una sobrecarga de la M o la totalidad de las sobrecargas de M tienen el mismo tipo de retorno?

El compilador puede averiguar el tipo de tiempo de compilación; el tipo de tiempo de compilación es dinámico, y el compilador se da cuenta de eso con éxito.

Creo que la pregunta que la intención de hacer es:

¿Por qué es el tipo de tiempo de compilación de M(dynamic_expression) siempre dinámica, incluso en el caso raro y poco probable de que usted está haciendo una llamada dinámica completamente innecesario un método M que siempre se elegirá independientemente del tipo de argumento?

Cuando formula la pregunta de esa manera, se responde a sí misma.:-)

Razón uno:

Los casos se imagina son raros; para que el compilador pueda realizar el tipo de inferencia que describe, se debe conocer suficiente información para que el compilador pueda hacer casi un análisis de tipo estático completo de la expresión. Pero si estás en ese escenario, ¿por qué estás usando la dinámica en primer lugar? Se podría hacer mucho mejor decir simplemente:

object d = whatever; 
Foo foo = new Foo(); 
int x = (d is string) ? foo.M((string)d) : foo((int)d); 

Obviamente, si sólo hay una sobrecarga de M, entonces es aún más fácil: convertir el objeto al tipo deseado. Si falla en tiempo de ejecución porque el lanzamiento fue malo, ¡bueno, la dinámica también habría fallado!

Simplemente no hay necesidad de dinámica en el primer lugar en este tipo de escenarios, ¿por qué habríamos de hacer un montón de caras y difíciles de trabajo inferencia de tipos en el compilador para permitir un escenario que no queremos que el uso dinámico para en primer lugar?

Razón dos:

supongamos que sí dijo que la resolución de sobrecarga tiene reglas muy especiales si el grupo método se conoce de forma estática para contener un método. Estupendo. Ahora acabamos de agregar un nuevo tipo de fragilidad al lenguaje. Ahora, agregar una nueva sobrecarga cambia el tipo de devolución de una llamada a un tipo completamente diferente, un tipo que no solo causa semántica dinámica, sino también tipos de valores de cuadros. ¡Pero espera, se pone peor!

// Foo corporation: 
class B 
{ 
} 

// Bar corporation: 
class D : B 
{ 
    public int M(int x) { return x; } 
} 

// Baz corporation: 
dynamic dyn = whatever; 
D d = new D(); 
var q = d.M(dyn); 

Supongamos que implementamos su función requiest e inferimos que q es int, por su lógica. Ahora corporación Foo añade:

class B 
{ 
    public string M(string x) { return x; } 
} 

Y de repente cuando corporación Baz vuelve a compilar su código, de repente, el tipo de q se convierte en voz baja dinámica, porque no sabemos en tiempo de compilación que dyn no es una cadena. ¡Eso es bizarre y cambio inesperado en el análisis estático! ¿Por qué un tercero agregar un nuevo método a una clase base hace que el tipo de una variable local cambie en un método completamente diferente en una clase completamente diferente que se escribe en una compañía diferente, una compañía que ni siquiera usa B directamente, pero solo a través de D?

Esta es una nueva forma del problema Clase de base quebradiza, y tratamos de minimizar los problemas de clase de base quebradiza en C#.

O, ¿y si en lugar de Foo Corp dijo:

class B 
{ 
    protected string M(string x) { return x; } 
} 

Ahora, por su lógica,

var q = d.M(dyn); 

da q el tipo int cuando el código anterior es fuera de un tipo que hereda de D, pero

var q = this.M(dyn); 

da el tipo de q como dinámico cuando dentro de un tipo que hereda de D! Como desarrollador, me parece bastante sorprendente.

Razón Tres:

hay demasiada inteligencia en C# ya. Nuestro objetivo no es construir un motor lógico que pueda resolver todas las restricciones de tipo posibles en todos los valores posibles dado un programa en particular. Preferimos tener reglas generales, comprensibles y comprensibles que se puedan anotar fácilmente e implementar sin errores. La especificación ya tiene ochocientas páginas y escribir un compilador sin errores es increíblemente difícil. No lo hagamos más difícil. Por no mencionar el costo de prueba todos esos casos locos.

Razón cuatro:

otra parte: el idioma que ofrece muchas oportunidades para servirse del analizador de tipo estático. Si está utilizando la dinámica, está específicamente solicitando que ese analizador difiera su acción hasta el tiempo de ejecución. No debería ser una sorpresa que el uso de la característica "dejar de hacer análisis de tipo estático en tiempo de compilación" haga que el análisis de tipo estático no funcione muy bien en tiempo de compilación.

+0

Considerando las mejores respuestas a [esta pregunta] (http://stackoverflow.com/q/3142495/1846281), puede obtener un objeto dinámico cuando deserialice una cadena JSON. En este caso, es posible que desee pasar este objeto dinámico a un único método (sin sobrecargas) que devuelva otro objeto tipado estáticamente (mapeo). Me parece extraño que el objeto devuelto por este método se considere dinámico, aunque la declaración del método establece de manera diferente. –

1

Sin embargo, ¿por qué el compilador no puede deducir el tipo de tiempo de compilación si M solo tiene una sobrecarga o todas las sobrecargas de M devuelven el mismo tipo?

El compilador podría potencialmente hacer esto, pero el equipo de traducción decidido no tener que funcione de esta manera.

Todo el propósito de dynamic es tener todas las expresiones que utilizan ejecución dinámica con "su resolución se difiere hasta que se ejecuta el programa" (C# spec, 4.2.3). El compilador no realiza explícitamente el enlace estático (que sería necesario para obtener el comportamiento que desea aquí) para las expresiones dinámicas.

Tener una alternativa al enlace estático si solo hubiera una opción de enlace obligaría al compilador a verificar este caso, que no se agregó. En cuanto a por qué el equipo de idioma no quiso hacerlo, sospecho Eric Lippert's response here aplica:

Me preguntan "¿por qué C no implementa la función X?" todo el tiempo. La respuesta es siempre la misma: porque nadie diseñó, especificó, implementó, probó, documentó y envió esa característica.

+0

"pero el equipo de idiomas decidió no hacerlo funcionar de esta manera". Esto es, efectivamente, lo que estoy preguntando, estoy preguntando por qué fue diseñado de esa manera, por lo que es una copia para decir que esa es la respuesta. "El compilador explícitamente no realiza el enlace estático (que sería necesario para obtener el comportamiento que desea aquí) para las expresiones dinámicas". De nuevo, eso es exactamente por lo que estoy preguntando; por qué fue diseñado de esa manera. "Sospecho que aquí se aplica la respuesta de Eric Lippert". Estoy seguro de que la respuesta siempre se aplica. Espero conocer un poco más. – jason

+1

@Jason Quizás alguien que el equipo de idiomas decidirá hacer sonar, pero sospecho que la respuesta será la misma que siempre dan: "porque nadie diseñó, especificó, implementó, probó, documentó y envió esa característica". Estoy de acuerdo, es una buena idea, pero también lo son muchas otras funciones del lenguaje que no hicieron, creo que simplemente no pensaron en eso o decidieron que no valía la pena implementarlo de esa manera ... –

+0

@Joey: No estoy seguro de si ese es el punto de 'dynamic', y ciertamente no estoy de acuerdo con la afirmación de" independientemente "; eso es básicamente asumir toda la pregunta. Pienso en el punto de 'dinámico' como posponer la comprobación de tipos en el tiempo de ejecución. Pero en los casos donde solo hay un tipo posible, como este, no veo por qué querríamos que se difiriera la verificación. – jason

1

Creo que el caso de ser capaz de determinar de forma estática el único tipo posible retorno de un método de resolución dinámica es tan estrecha que sería más confuso e inconsistente si el compilador de C# lo hizo, en lugar de tener todos los ámbitos comportamiento.

Incluso con su ejemplo, ¿qué pasaría si Foo es parte de un archivo DLL diferente, Foo podría ser una versión más reciente en tiempo de ejecución de una redirección de enlace con M 's adicionales que tienen un tipo de retorno diferente, y entonces el compilador habría adivinado mal porque la resolución del tiempo de ejecución devolvería un tipo diferente.

¿Qué pasa si Foo es un IDynamicMetaObjectProvider d podría no coincide con ninguno de los argumentos estáticos y por lo tanto sería caer de nuevo que es un comportamiento dinámico que podría devolver un tipo diferente.

3

Un diseño inicial de la característica dynamic tenía soporte para algo como esto. El compilador seguiría teniendo resolución de sobrecarga estática e introdujo una "sobrecarga fantasma" que representa la resolución de sobrecarga dinámica solo si es necesario.

Como se puede ver en el segundo puesto, este enfoque presenta una gran complejidad (el segundo artículo habla de cómo necesitaría la inferencia de tipos a ser modificados para hacer que el enfoque de trabajo). No me sorprende que el equipo de C# haya decidido usar la idea más simple de usar siempre una resolución de sobrecarga dinámica cuando se trata de dynamic.

Cuestiones relacionadas