2010-08-28 23 views
5

¿Podría darme algunas razones para las limitaciones del tipo dinámico en C#? Leí sobre ellos en "Pro C# 2010 y la plataforma .NET 4". He aquí un extracto (si citar los libros es ilegal aquí, dime y quitaré el extracto):Limitaciones del tipo dinámico en C#

Mientras que un gran número de cosas pueden ser definido usando la palabra clave dinámica, hay algunas limitaciones en cuanto a su uso. Si bien no muestran los topes , sabe que un elemento de datos dinámicos no puede usar las expresiones lambda o los métodos anónimos de C# al llamar a un método. Por ejemplo, el siguiente código siempre dará como resultado en errores, incluso si el método de destino toma un parámetro delegado que toma un valor de cadena y devuelve void.

dynamic a = GetDynamicObject(); 
// Error! Methods on dynamic data can’t use lambdas! 
a.Method(arg => Console.WriteLine(arg)); 

Para eludir esta restricción, tendrá que trabajar con el delegado subyacente directamente, usando las técnicas descritas en el Capítulo 11 ( métodos anónimos y expresiones lambda , etc.). Otra limitación es que un punto de datos dinámico no puede comprender ningún método de extensión (consulte Capítulo 12). Desafortunadamente, esto incluiría cualquiera de los métodos de extensión que provienen de las API de LINQ. Por lo tanto, una variable declarada con la palabra clave dinámica ha muy limitada uso dentro de LINQ a objetos y otras tecnologías LINQ:

dynamic a = GetDynamicObject(); 
// Error! Dynamic data can’t find the Select() extension method! 
var data = from d in a select d; 

Gracias de antemano.

+0

Eso es obvio, un objeto dinámico no tiene una propiedad llamada 'Método' y no es un' IEnumarable'. No veo esas cosas que mencionas como limitaciones, tal vez tú y el autor no entendieron lo que 'dinámico 'hace. – BrunoLM

+1

No lo creo. La validez de los miembros (como "Método") no será verificada por el compilador. –

Respuesta

16

Las conjeturas de Tomás son bastante buenas. Su razonamiento sobre los métodos de extensión es perfecto. Básicamente, para hacer que los métodos de extensión funcionen necesitamos que el sitio de llamadas en tiempo de ejecución sepa de alguna manera qué instrucciones de uso estaban en vigor en el momento de la compilación.Simplemente no tuvimos suficiente tiempo ni presupuesto para desarrollar un sistema por el cual esta información pudiera persistir en el sitio de llamadas.

Para lambdas, la situación es en realidad más compleja que el simple problema de determinar si la lambda va a árbol de expresión o delegar. Tenga en cuenta lo siguiente:

d.M(123) 

donde d es una expresión de tipo dinámico. * ¿Qué objeto debería pasar en el tiempo de ejecución como argumento para el sitio de llamada "M"? Claramente, colocamos 123 y lo pasamos. Luego, el algoritmo de resolución de sobrecarga en el enlazador de tiempo de ejecución mira el tipo de tiempo de ejecución de dy el tipo de tiempo de compilación de int 123 y funciona con eso.

Ahora ¿Y si era

d.M(x=>x.Foo()) 

Ahora qué objeto debemos pasar como argumento? No tenemos forma de representar el "método lambda de una variable que llama a una función desconocida llamada Foo en lo que sea que sea el tipo de x".

Supongamos que queremos implementar esta característica: ¿qué tendríamos que implementar? En primer lugar, necesitaríamos una forma de representar un sin vinculación lambda. Los árboles de expresión son por diseño solo para representar lambdas, donde se conocen todos los tipos y métodos. Tendríamos que inventar un nuevo tipo de árbol de expresiones "sin tipo". Y luego tendríamos que implementar todos de las reglas para el enlace lambda en el enlace de tiempo de ejecución.

Considera ese último punto. Lambdas puede contener declaraciones. La implementación de esta función requiere que el archivo de tiempo de ejecución contenga todo el analizador semántico para cada posible declaración en C#.

Eso fue en órdenes de magnitud fuera de nuestro presupuesto. Todavía estaríamos trabajando en C# 4 hoy si hubiéramos querido implementar esa característica.

Desafortunadamente, esto significa que LINQ no funciona muy bien con la dinámica, porque LINQ por supuesto usa lambdas sin tipo en todo el lugar. Es de esperar que en alguna versión futura hipotética de C# tengamos un encuadernador de tiempo de ejecución más completo y la capacidad de hacer representaciones homoicónicas de lambdas independientes. Pero no aguantaría la respiración esperando si fuera tú.

ACTUALIZACIÓN: Un comentario solicita una aclaración sobre el punto sobre el analizador semántico.

considerar las siguientes sobrecargas:

class C { 
    public void M(Func<IDisposable, int> f) { ... } 
    public void M(Func<int, int> f) { ... } 
    ... 
} 

y una llamada

d.M(x=> { using(x) { return 123; } }); 

Supongamos que D es del tipo de compilación de tiempo C. dinámica y tipo de ejecución ¿Qué debe hacer el aglutinante de tiempo de ejecución?

El aglutinante tiempo de ejecución debe determinar en tiempo de ejecución si la expresión x=>{...} es convertible a cada uno de los tipos de delegado en cada una de las sobrecargas de M.

Con el fin de hacer que, el ligante de tiempo de ejecución debe ser capaz de determinar que la segunda sobrecarga no es aplicable.Si fuera aplicable, entonces podría tener un int como argumento para una instrucción using, pero el argumento para una instrucción using debe ser desechable. Esto significa que la carpeta de tiempo de ejecución debe conocer todas las reglas para la instrucción using y poder informar correctamente si un posible uso de la instrucción using es legal o ilegal.

Está claro que no está restringido a la instrucción using. El encuadernador de tiempo de ejecución debe conocer todas las reglas para C# para determinar si una declaración determinada lambda es convertible a un tipo de delegado determinado.

No tuvimos tiempo para escribir una carpeta de tiempo de ejecución que era esencialmente un compilador de C# completamente nuevo que genera árboles DLR en lugar de IL. Al no permitir lambdas, solo tenemos que escribir un encuadernador de tiempo de ejecución que sepa cómo enlazar llamadas a métodos, expresiones aritméticas y algunos otros tipos simples de sitios de llamadas. Permitir lambdas hace que el problema del enlace en tiempo de ejecución sea del orden de docenas o cientos de veces más costoso de implementar, probar y mantener.

+0

Muchas gracias por una respuesta detallada. Podría explicar la parte "Considere ese último punto. Lambdas puede contener declaraciones. La implementación de esta característica requiere que el encuadernador de tiempo de ejecución contenga todo el analizador semántico para cada declaración posible en C#." un poco más claro? –

9

Lambdas: Creo que una razón para no apoyar lambdas como parámetros para objetos dinámicos es que el compilador no sabría si compilar el lambda como un delegado o como un árbol de expresión.

Cuando utiliza un lambda, el compilador decide en función del tipo del parámetro o variable de destino. Cuando es Func<...> (u otro delegado) compila el lambda como un delegado ejecutable. Cuando el objetivo es Expression<...>, compila lambda en un árbol de expresiones.

Ahora, cuando tiene un tipo dynamic, no sabe si el parámetro es delegado o expresión, por lo que el compilador no puede decidir qué hacer.

Métodos de extensión: Creo que la razón aquí es que encontrar métodos de extensión en tiempo de ejecución sería bastante difícil (y quizás también ineficiente). En primer lugar, el tiempo de ejecución necesitaría saber a qué espacios de nombres se hizo referencia utilizando using. Entonces necesitaría buscar todas las clases en todos los ensamblajes cargados, filtrar aquellos que son accesibles (por espacio de nombres) y luego buscarlos para métodos de extensión ...

+0

Gracias. ¿Puede el tiempo de ejecución decidir si el parámetro es delegado o expresión? Y la razón principal es que la implementación de esas características será costosa? Por cierto, es impresionante que seas tan joven y que hayas escrito un libro. –

+0

El compilador debe conocer si el parámetro es un delegado o una expresión (para que pueda compilar el código adecuadamente). En principio, el compilador podría hacer ambas cosas para los tipos dinámicos (y el tiempo de ejecución podría entonces decidir), pero supongo que esto sería demasiado trabajo ... –

3

Eric (y Tomas) lo dice bien, pero así es como lo pienso.

Esta instrucción #

a.Method(arg => Console.WriteLine(arg)); 

C no tiene sentido sin una gran cantidad de contexto. Las expresiones Lambda no tienen tipos, sino que son convertibles a delegate (o Expression). Por lo tanto, la única forma de recopilar el significado es proporcionar algún contexto que obligue a la lambda a convertirse a un tipo de delegado específico. Ese contexto es típicamente (como en este ejemplo) resolución de sobrecarga; dado el tipo de a, y las sobrecargas disponibles Method en ese tipo (incluidos los miembros de la extensión), podemos colocar posiblemente algún contexto que dé el significado lambda.

Sin ese contexto para producir el significado, terminas teniendo que agrupar todo tipo de información sobre la lambda con la esperanza de vincular de algún modo las incógnitas en tiempo de ejecución. (¿Qué IL podría posiblemente generar?)

En gran contraste, uno se pone un tipo de delegado específica allí,

a.Method(new Action<int>(arg => Console.WriteLine(arg))); 

Kazam! Las cosas se pusieron fáciles. No importa qué código esté dentro de la lambda, ahora sabemos exactamente qué tipo tiene, lo que significa que podemos compilar IL como lo haríamos con cualquier cuerpo de método (ahora sabemos, por ejemplo, cuál de las muchas sobrecargas de Console.WriteLine llamamos) Y ese código tiene un tipo específico (Action<int>), lo que significa que es fácil para el encuadernador de tiempo de ejecución ver si a tiene un Method que toma ese tipo de argumento.

En C#, una lambda desnuda es casi insignificante. C# lambdas necesita un contexto estático para darles significado y descartar las ambigüedades que surgen de muchas posibles coercisiones y sobrecargas. Un programa típico proporciona este contexto con facilidad, pero el caso dynamic carece de este contexto importante.

+0

Estoy volviendo a leer mi respuesta, y me doy cuenta de que el cuerpo 'Console.WriteLine (arg)' probablemente no sea un buen ejemplo de cómo "¿cómo se vería la IL?" una pregunta difícil, porque creo que es simple: parece IL cuando 'arg' tiene tipo' dynamic'. Entonces la respuesta de Eric está llena de mejores ejemplos de lo que es realmente difícil. Creo. También podría estar equivocado. :) Sin embargo, siento que mi respuesta comunica la esencia tal como está, así que lo dejo tal como está. – Brian

+0

Gracias. Todas las opiniones son bienvenidas :). –