¿Para qué se utiliza el tipo "dinámico" en C#4.0?
.net dynamic (10)
- Puede llamar a lenguajes dinámicos como CPython usando pythonnet:
dynamic np = Py.Import("numpy")
- Puede aplicar genéricos a
dynamic
al aplicar operadores numéricos en ellos. Esto proporciona seguridad de tipo y evita las limitaciones de los genéricos. Esto es en esencia * tipa pato:
T y = x * (dynamic)x
, donde typeof(x) is T
C # 4.0 introdujo un nuevo tipo llamado ''dinámico''. Todo suena bien, pero ¿para qué lo usaría un programador?
¿Hay alguna situación en la que pueda salvar el día?
El mejor caso de uso de variables de tipo ''dinámico'' 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;
}
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 compilación.
Hace que sea más fácil para los lenguajes de escritura estática (CLR) interoperar con los dinámicos (python, ruby ...) que se ejecutan en el DLR (tiempo de ejecución de lenguaje dinámico), consulte MSDN .
Interoperabilidad COM Especialmente IUnknown. Fue diseñado especialmente para eso.
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:
- Argumentos opcionales
- Haciendo la
ref
opcional para las API COM - Argumentos con nombre
La nueva sintaxis 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 compilador 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 sintaxis 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 compilador de que compilara 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 reflejo 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 dinámicos, 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:
- La nueva palabra clave en C #,
dynamic
- 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 programació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 compilarlo, 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:
La palabra clave dinámica es nueva en C # 4.0 y se usa para indicar al compilador 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 dinámico, 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 compilador. Esto significa que la llamada cust.MissingMethod () compilará 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 programadores 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 compila porque 2.5 se escribe como un doble y la línea 3 no se compila 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
Me sorprende que nadie haya mencionado envíos múltiples . La forma habitual de evitar esto es a través del patrón de visitante y eso no siempre es posible, por lo que terminas con cheques apilados.
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());
}
Tú lo haces:
public static MapDtoBase CreateDto(ChartItem item)
{
return CreateDtoImpl(item as dynamic);
}
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.
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.
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 "dinámico", su código es mucho más elegante:
static DateTime RetrieveValueOfCreationDate(dynamic item)
{
return item.CreationDate;
}