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?
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?
Respuestas:
La palabra clave dinámica es nueva en C # 4.0 y se usa para decirle al compilador que el tipo de una variable puede cambiar o que no se conoce hasta el tiempo de ejecución. Piense en ello como si pudiera 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 al cliente como tipo Cliente. Debido a que lo declaramos dinámico, el tiempo de ejecución se hace cargo y luego busca y establece la propiedad FirstName para nosotros. Ahora, por supuesto, cuando está utilizando una variable dinámica, está renunciando a la verificación del tipo de compilador. Esto significa que la llamada cust.MissingMethod () se compilará y no fallará hasta el tiempo de ejecución. El resultado de esta operación es una RuntimeBinderException porque MissingMethod no está definido en la clase Customer.
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 maneras de aprovechar esto, pero he estado usando C # tanto tiempo que me parece "incorrecto".
dynamic foo = 123;
foo = "bar";
OK, lo más probable es que no escribas código como el anterior muy a menudo. Sin embargo, puede haber ocasiones en que la reutilización de variables pueda ser útil o limpiar una pieza sucia de código heredado. Un caso simple con el que me encuentro a menudo es tener que emitir 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 doble y la línea 3 no se compila porque Math.Sqrt espera un doble. Obviamente, todo lo que tiene que hacer es emitir y / o cambiar su tipo de variable, pero puede haber situaciones en las que sea dinámico usarlo.
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
dynamic
in c # para resolver problemas que pueden resolverse (tal vez incluso mejor) mediante las características estándar de c # y la escritura estática, o como máximo con inferencia de tipos ( var
). dynamic
debe solamente ser utilizado cuando se trata de cuestiones interoperabilty con el DLR. Si escribe código en un lenguaje tipado estático, como c # is, entonces hágalo y no emule un lenguaje dinámico. Eso es simplemente feo.
dynamic
variables en su código donde no las necesita (como en su ejemplo con la raíz cuadrada), abandona la comprobación de errores de tiempo de compilación limpio; en cambio, ahora está recibiendo posibles errores de tiempo de ejecución.
La dynamic
palabra clave se agregó, junto con muchas otras características nuevas de C # 4.0, para que sea más fácil hablar con el 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 Word.Application
objeto, 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ía algo como esto (estoy simplificando, este no es el 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 ya que C # antes de la versión 4.0 no tenía una noción de argumentos opcionales. En C # 4.0, las API COM se han hecho más fáciles de trabajar al introducir:
ref
opcional para las API COMLa nueva sintaxis para la llamada anterior sería:
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
¿Ves cuánto más fácil se ve, cuánto más legible se vuelve?
Vamos a separar 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 le ha ocultado, ahora puede concentrarse en el qué , y no tanto sobre cómo . A Anders Hejlsberg le gusta decir que tienes que invocar diferentes "encantamientos", que es una especie de juego de palabras con la magia de todo el asunto, en el que normalmente tienes que agitar las manos y decir algunas palabras mágicas en el orden correcto. para poner en marcha cierto tipo de hechizo. La antigua forma de API de hablar con objetos COM era mucho de eso, necesitabas saltar muchos aros para convencer al compilador de que compilara el código por ti.
Las cosas se descomponen en C # antes de la versión 4.0 aún más si intentas hablar con un objeto COM para el que no tienes una interfaz o clase, todo lo que tienes es una IDispatch
referencia.
Si no sabe qué es, IDispatch
es básicamente un reflejo para los objetos COM. Con una IDispatch
interfaz, puede preguntarle al objeto "cuál es el número de identificación del método conocido como Guardar", y construir matrices de cierto tipo que contengan los valores del argumento, y finalmente llamar a un Invoke
método en la IDispatch
interfaz para llamar al método, pasando todo la información que has logrado buscar juntos.
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 por solo abrir un documento.
VB tenía argumentos opcionales y soporte para la mayoría de esto fuera de la caja hace mucho tiempo, por lo que este código C #:
wordApplication.Documents.Open(@"C:\Test.docx", ReadOnly: true);
es básicamente solo C # ponerse al día con VB en términos de expresividad, pero hacerlo de la manera correcta, al hacerlo extensible, y no solo para COM. Por supuesto, esto también está disponible para VB.NET o cualquier otro lenguaje creado sobre el tiempo de ejecución de .NET.
Puede encontrar más información sobre la IDispatch
interfaz en Wikipedia: IDispatch si desea leer más al respecto. Es realmente algo sangriento.
Sin embargo, ¿qué pasaría si quisieras hablar con un objeto de Python? Hay una API diferente para la que se usa para los objetos COM, y dado que los objetos de Python también son de naturaleza dinámica, debe 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:
dynamic
dynamic
requiere la palabra clave y asigna las llamadas a la forma correcta 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.La dynamic
palabra clave no está, sin embargo, la intención de reemplazar cualquier código .NET de sólo existente. Claro, puede hacerlo, pero no se agregó por esa razón, y los autores del lenguaje de programación C # con Anders Hejlsberg en el frente, han insistido firmemente en que todavía consideran que C # es un lenguaje fuertemente tipado y no sacrificarán ese principio
Esto significa que aunque puede 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 concebido como una especie de sistema de magia que permite descubrir qué querías decir en tiempo de ejecución.
Todo el propósito era facilitar el hablar con otros tipos de objetos.
Hay mucho material en Internet sobre la palabra clave, los proponentes, los opositores, las discusiones, las críticas, los elogios, etc.
Le sugiero que comience con los siguientes enlaces y luego busque en Google más:
dynamic
se agregó, para apoyar a otros ecosistemas sobre cómo se puede realizar la invocación de métodos de reflexión, así como proporcionar una especie de enfoque de recuadro negro a las estructuras de datos con una forma documentada de lograr esto.
Me sorprende que nadie haya mencionado el envío múltiple . 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 is
cheques apilados .
Así que aquí hay un ejemplo de la vida real de una aplicación propia. 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 una subclase de MapPoint
y si no se coloca antes MapPoint
, nunca será alcanzado. Este no es el caso con dinámico, ya que se llamará al método de coincidencia más cercano.
Como puede adivinar por el código, esa característica fue útil mientras realizaba la traducción de objetos ChartItem a sus versiones serializables. No quería contaminar mi código con visitantes y tampoco quería contaminar mis ChartItem
objetos con atributos específicos de serialización inútiles.
is
apilado uno encima del otro.
magic
; no hay tal cosa como magia.
Facilita la interoperabilidad de los lenguajes de tipo estático (CLR) con los dinámicos (python, ruby ...) que se ejecutan en el DLR (tiempo de ejecución del lenguaje dinámico), 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, puede usar el siguiente código en su lugar para la misma operación.
scriptobj.Count += 1;
MSDN enumera estas ventajas:
- Simplifica la transferencia de lenguajes dinámicos a .NET Framework
- Habilita características dinámicas en lenguajes estáticamente escritos
- Proporciona beneficios futuros de DLR y .NET Framework
- Permite compartir bibliotecas y objetos
- Proporciona envío dinámico rápido e invocación
Ver MSDN para más detalles.
Un ejemplo de uso:
Consume muchas clases que tienen una propiedad comunitaria '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 commun que recupera el valor de la propiedad 'CreationDate', debería usar la reflexión:
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;
}
Las víctimas de RAD y Python la usarán principalmente para destruir la calidad del código, IntelliSense y la detección de errores en tiempo de compilación.
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 necesite. Úselo como último recurso; es beneficioso, pero escuché que muchas cosas pasan debajo de las escenas en términos de IL generada y eso puede tener un precio de rendimiento.
El mejor caso de uso de las 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 empresarial. 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 ). 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;
}
dynamic np = Py.Import("numpy")
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 * pato escribiendo:T y = x * (dynamic)x
, dónde typeof(x) is T
Otro caso de uso para dynamic
escribir es para métodos virtuales que experimentan un problema con covarianza o contravarianza. Un ejemplo de ello es el Clone
método infame que devuelve un objeto del mismo tipo que el objeto al que se llama. Este problema no se resuelve por completo con un retorno dinámico porque omite la comprobación de tipos estáticos, pero al menos no es necesario usar conversiones feas todo el tiempo según cuando se usa plain object
. De lo contrario, los moldes se vuelven implícitos.
public class A
{
// attributes and constructor here
public virtual dynamic Clone()
{
var clone = new A();
// Do more cloning stuff here
return clone;
}
}
public class B : A
{
// more attributes and constructor here
public override dynamic Clone()
{
var clone = new B();
// Do more cloning stuff here
return clone;
}
}
public class Program
{
public static void Main()
{
A a = new A().Clone(); // No cast needed here
B b = new B().Clone(); // and here
// do more stuff with a and b
}
}