¿Para qué se utiliza el tipo “dynamic” en C # 4.0?

C # 4.0 introdujo un nuevo tipo llamado ‘dynamic‘. Todo suena bien, pero ¿para qué lo usaría un progtwigdor?

¿Hay alguna situación en la que pueda salvar el día?

La palabra clave dinámica es nueva en C # 4.0 y se usa para indicar al comstackdor que el tipo de variable puede cambiar o que no se conoce hasta el tiempo de ejecución. Piense que es capaz de interactuar con un Objeto sin tener que lanzarlo.

dynamic cust = GetCustomer(); cust.FirstName = "foo"; // works as expected cust.Process(); // works as expected cust.MissingMethod(); // No method found! 

Tenga en cuenta que no necesitamos emitir ni declarar cust como tipo Cliente. Como lo declaramos dynamic, el tiempo de ejecución toma el control y luego busca y establece la propiedad FirstName para nosotros. Ahora, por supuesto, cuando está usando una variable dinámica, está renunciando a la verificación del tipo de comstackdor. Esto significa que la llamada cust.MissingMethod () comstackrá y no fallará hasta el tiempo de ejecución. El resultado de esta operación es RuntimeBinderException porque MissingMethod no está definido en la clase Cliente.

El ejemplo anterior muestra cómo funciona la dinámica al llamar a métodos y propiedades. Otra característica poderosa (y potencialmente peligrosa) es poder reutilizar variables para diferentes tipos de datos. Estoy seguro de que los progtwigdores de Python, Ruby y Perl pueden pensar en un millón de formas de aprovechar esto, pero he estado usando C # por tanto tiempo que me parece “incorrecto”.

 dynamic foo = 123; foo = "bar"; 

De acuerdo, probablemente no escriba código como el anterior muy a menudo. Sin embargo, puede haber ocasiones en que la reutilización variable puede ser útil o limpiar una pieza sucia de código heredado. Un caso simple con el que me encuentro a menudo es tener que lanzar constantemente entre decimal y doble.

 decimal foo = GetDecimalValue(); foo = foo / 2.5; // Does not compile foo = Math.Sqrt(foo); // Does not compile string bar = foo.ToString("c"); 

La segunda línea no se comstack porque 2.5 se escribe como un doble y la línea 3 no se comstack porque Math.Sqrt espera un doble. Obviamente, todo lo que tiene que hacer es transmitir y / o cambiar su tipo de variable, pero puede haber situaciones en las que tiene sentido usar la dinámica.

 dynamic foo = GetDecimalValue(); // still returns a decimal foo = foo / 2.5; // The runtime takes care of this for us foo = Math.Sqrt(foo); // Again, the DLR works its magic string bar = foo.ToString("c"); 

Leer más característica: http://www.codeproject.com/KB/cs/CSharp4Features.aspx

La palabra clave dynamic se agregó, junto con muchas otras características nuevas de C # 4.0, para que sea más sencillo hablar con un código que vive o proviene de otros tiempos de ejecución, que tiene diferentes API.

Toma un ejemplo.

Si tiene un objeto COM, como el objeto Word.Application , y desea abrir un documento, el método para hacerlo viene con no menos de 15 parámetros, la mayoría de los cuales son opcionales.

Para llamar a este método, necesitarías algo como esto (estoy simplificando, esto no es código real):

 object missing = System.Reflection.Missing.Value; object fileName = "C:\\test.docx"; object readOnly = true; wordApplication.Documents.Open(ref fileName, ref missing, ref readOnly, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing); 

Tenga en cuenta todos esos argumentos? Debe pasarlos desde C # antes de que la versión 4.0 no tenga una noción de argumentos opcionales. En C # 4.0, se ha hecho más fácil trabajar con las API COM introduciendo:

  1. Argumentos opcionales
  2. Haciendo la ref opcional para las API COM
  3. Argumentos con nombre

La nueva syntax para la llamada anterior sería:

 wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true); 

Vea cuánto más fácil se ve, ¿cuánto más legible se vuelve?

Vamos a romper eso:

  named argument, can skip the rest | v wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true); ^ ^ | | notice no ref keyword, can pass actual parameter values instead 

La magia es que el comstackdor de C # ahora inyectará el código necesario y trabajará con nuevas clases en el tiempo de ejecución, para hacer casi exactamente lo mismo que antes, pero la syntax se ha ocultado, ahora puede enfocarse en el qué , y no tanto sobre cómo . A Anders Hejlsberg le gusta decir que tienes que invocar diferentes “hechizos”, que es una especie de juego de palabras con la magia de todo el asunto, donde normalmente tienes que agitar tu (s) mano (s) y decir algunas palabras mágicas en el orden correcto. para obtener un cierto tipo de hechizo. La antigua forma de API de hablar con objetos COM era mucho de eso, era necesario pasar por muchos aros para convencer al comstackdor de que comstackra el código por usted.

Las cosas se descomponen en C # antes de la versión 4.0, incluso más si intentas hablar con un objeto COM para el que no tienes una interfaz o clase, todo lo que tienes es una referencia IDispatch .

Si no sabes de qué se trata, IDispatch es básicamente reflection de los objetos COM. Con una interfaz IDispatch puede preguntar al objeto “cuál es el número de identificación para el método conocido como Guardar”, y crear matrices de un cierto tipo que contiene los valores del argumento, y finalmente invocar un método Invoke en la interfaz IDispatch para llamar al método , pasando toda la información que ha logrado juntar.

El método Save anterior podría verse así (definitivamente este no es el código correcto):

 string[] methodNames = new[] { "Open" }; Guid IID = ... int methodId = wordApplication.GetIDsOfNames(IID, methodNames, methodNames.Length, lcid, dispid); SafeArray args = new SafeArray(new[] { fileName, missing, missing, .... }); wordApplication.Invoke(methodId, ... args, ...); 

Todo esto solo por abrir un documento.

VB tenía argumentos opcionales y soporte para la mayor parte de esto fuera de la caja hace mucho tiempo, por lo que este código C #:

 wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true); 

Básicamente, es solo C # alcanzar a VB en términos de expresividad, pero hacerlo de la manera correcta, haciéndolo extensible, y no solo para COM. Por supuesto, esto también está disponible para VB.NET o cualquier otro lenguaje creado sobre .NET runtime.

Puede encontrar más información sobre la interfaz IDispatch en Wikipedia: IDispatch si desea leer más al respecto. Es realmente algo sangriento.

Sin embargo, ¿qué pasa si quieres hablar con un objeto de Python? Hay una API diferente para eso que la que se usa para los objetos COM, y dado que los objetos Python también son dynamics, debes recurrir a la magia de reflexión para encontrar los métodos correctos para llamar, sus parámetros, etc., pero no el .NET reflexión, algo escrito para Python, muy parecido al código IDispatch anterior, simplemente completamente diferente.

Y para Ruby? Una API diferente todavía.

JavaScript? Mismo trato, API diferente para eso también.

La palabra clave dinámica consta de dos cosas:

  1. La nueva palabra clave en C #, dynamic
  2. Un conjunto de clases de tiempo de ejecución que sabe cómo tratar con los diferentes tipos de objetos, que implementan una API específica que requiere la palabra clave dynamic y asigna las llamadas al modo correcto de hacer las cosas. La API incluso está documentada, por lo que si tiene objetos que provienen de un tiempo de ejecución no cubierto, puede agregarlo.

Sin embargo, la palabra clave dynamic no pretende reemplazar ningún código existente de .NET. Claro, puedes hacerlo, pero no fue agregado por esa razón, y los autores del lenguaje de progtwigción C # con Anders Hejlsberg al frente, han sido más firmes en que aún consideran C # como un lenguaje fuertemente tipado, y no sacrificarán ese principio.

Esto significa que aunque puedes escribir código como este:

 dynamic x = 10; dynamic y = 3.14; dynamic z = "test"; dynamic k = true; dynamic l = x + y * z - k; 

y comstackrlo, no fue una especie de tipo de sistema de magia que permite imaginar qué fue lo que querías decir en el tiempo de ejecución.

Todo el propósito fue facilitar el diálogo con otros tipos de objetos.

Hay un montón de material en Internet sobre la palabra clave, los defensores, los opositores, las discusiones, las difamaciones, los elogios, etc.

Te sugiero que comiences con los siguientes enlaces y luego busca más en Google:

  • DevDays 2010: Anders Hejlsberg – C # 4.0 y más allá
  • Canal 9: Mads Torgersen – Inside C # 4.0: escritura dinámica + +
  • DevX: COM Interop se pone mucho mejor en C # 4.0
  • Scott Hanselman – C # 4 y la palabra clave dinámica – Whirlwind Tour alrededor de .NET 4 (y Visual Studio 2010) Beta 1

Me sorprende que nadie haya mencionado envíos múltiples . La forma habitual de evitar esto es a través del patrón Visitor (doble envío) y eso no siempre es posible, por lo que terminas con astackdos.

Así que aquí hay un ejemplo de la vida real de una aplicación mía. En lugar de hacer:

 public static MapDtoBase CreateDto(ChartItem item) { if (item is ElevationPoint) return CreateDtoImpl((ElevationPoint)item); if (item is MapPoint) return CreateDtoImpl((MapPoint)item); if (item is MapPolyline) return CreateDtoImpl((MapPolyline)item); //other subtypes follow throw new ObjectNotFoundException("Counld not find suitable DTO for " + item.GetType()); } 

Utiliza dynamic para llamar a su función de fábrica CreateDtoImpl con el tipo de tiempo de ejecución (dynamic) del objeto:

 public static MapDtoBase CreateDto(ChartItem item) { return CreateDtoImpl(item as dynamic); // magic here } private static MapDtoBase CreateDtoImpl(ChartItem item) { throw new ObjectNotFoundException("Counld not find suitable DTO for " + item.GetType()); } private static MapDtoBase CreateDtoImpl(MapPoint item) { return new MapPointDto(item); } private static MapDtoBase CreateDtoImpl(ElevationPoint item) { return new ElevationDto(item); } 

Tenga en cuenta que en el primer caso, ElevationPoint es la subclase de MapPoint y, si no se coloca antes de MapPoint , nunca se alcanzará. Este no es el caso con la dinámica, ya que se llamará al método de comparación más cercano.

Como se puede adivinar por el código, esa función fue útil mientras estaba realizando la traducción de objetos ChartItem a sus versiones serializables. No quería contaminar mi código con los visitantes y tampoco quería contaminar mis objetos ChartItem con atributos específicos de serialización inútil.

Hace que sea más fácil para los lenguajes de escritura estática (CLR) interoperar con los dynamics (python, ruby ​​…) que se ejecutan en el DLR (tiempo de ejecución de lenguaje dynamic), consulte MSDN :

Por ejemplo, puede usar el siguiente código para incrementar un contador en XML en C #.

 Scriptobj.SetProperty("Count", ((int)GetProperty("Count")) + 1); 

Al usar el DLR, podría usar el siguiente código para la misma operación.

 scriptobj.Count += 1; 

MSDN enumera estas ventajas:

  • Simplifica la transferencia de idiomas dynamics al .NET Framework
  • Habilita características dinámicas en idiomas con escritura estática
  • Proporciona beneficios futuros del DLR y .NET Framework
  • Permite el intercambio de bibliotecas y objetos
  • Proporciona Despacho dynamic rápido y la Invocación

Ver MSDN para más detalles.

Un ejemplo de uso:

Consume muchas clases que tienen una propiedad común ‘CreationDate’:

 public class Contact { // some properties public DateTime CreationDate { get; set; } } public class Company { // some properties public DateTime CreationDate { get; set; } } public class Opportunity { // some properties public DateTime CreationDate { get; set; } } 

Si escribe un método común que recupera el valor de la propiedad ‘CreationDate’, debería usar reflection:

  static DateTime RetrieveValueOfCreationDate(Object item) { return (DateTime)item.GetType().GetProperty("CreationDate").GetValue(item); } 

Con el concepto “dynamic”, su código es mucho más elegante:

  static DateTime RetrieveValueOfCreationDate(dynamic item) { return item.CreationDate; } 

Interoperabilidad COM Especialmente IUnknown. Fue diseñado especialmente para eso.

En su mayoría será utilizado por las víctimas de RAD y Python para destruir la calidad del código, IntelliSense y la detección de errores de tiempo de comstackción.

El mejor caso de uso de variables de tipo ‘dynamic’ para mí fue cuando, recientemente, estaba escribiendo una capa de acceso a datos en ADO.NET ( usando SQLDataReader ) y el código invocaba los procedimientos almacenados heredados ya escritos. Hay cientos de esos procedimientos almacenados heredados que contienen la mayor parte de la lógica comercial. Mi capa de acceso a datos necesitaba devolver algún tipo de datos estructurados a la capa de lógica de negocios, basada en C #, para hacer algunas manipulaciones ( aunque casi no hay ninguna ). Cada procedimiento almacenado devuelve un conjunto diferente de datos ( columnas de tabla ). Entonces, en lugar de crear docenas de clases o estructuras para contener los datos devueltos y pasarlos al BLL, escribí el siguiente código que se ve bastante elegante y ordenado.

 public static dynamic GetSomeData(ParameterDTO dto) { dynamic result = null; string SPName = "a_legacy_stored_procedure"; using (SqlConnection connection = new SqlConnection(DataConnection.ConnectionString)) { SqlCommand command = new SqlCommand(SPName, connection); command.CommandType = System.Data.CommandType.StoredProcedure; command.Parameters.Add(new SqlParameter("@empid", dto.EmpID)); command.Parameters.Add(new SqlParameter("@deptid", dto.DeptID)); connection.Open(); using (SqlDataReader reader = command.ExecuteReader()) { while (reader.Read()) { dynamic row = new ExpandoObject(); row.EmpName = reader["EmpFullName"].ToString(); row.DeptName = reader["DeptName"].ToString(); row.AnotherColumn = reader["AnotherColumn"].ToString(); result = row; } } } return result; } 
  1. Puede llamar a lenguajes dynamics como CPython usando pythonnet:

dynamic np = Py.Import("numpy")

  1. Puede aplicar generics a dynamic al aplicar operadores numéricos en ellos. Esto proporciona seguridad de tipo y evita las limitaciones de los generics. Esto es en esencia * tipa pato:

T y = x * (dynamic)x , donde typeof(x) is T

Se evalúa en tiempo de ejecución, por lo que puede cambiar el tipo como puede en JavaScript a lo que desee. Esto es legitimo:

 dynamic i = 12; i = "text"; 

Y para que pueda cambiar el tipo que necesita. Úselo como último recurso; es beneficioso, pero he oído que muchas cosas suceden debajo de las escenas en términos de IL generado y que pueden tener un precio de rendimiento.