¿Para qué se usa el tipo 'dinámico' en C # 4.0?


236

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?



Es útil cuando se trabaja con COM o idiomas escritos dinámicamente. Por ejemplo, si usaría lua o python para escribir su lenguaje, es muy conveniente llamar al código de scripting como si fuera un código normal.
CodesInChaos


Espero que este artículo tenga una respuesta completa a su pregunta visualstudiomagazine.com/Articles/2011/02/01/…
Desarrollador

Respuestas:


196

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


97
Personalmente, no me gusta la idea de usar dynamicin 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). dynamicdebe 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.
Philip Daubmeier

40
Si hace un uso intensivo de las dynamicvariables 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.
Philip Daubmeier

33
Mayormente bien, pero un par de errores menores. Primero, no es correcto decir que dinámico significa que el tipo de variable puede cambiar. La variable en cuestión es de tipo "dinámico" (desde la perspectiva del lenguaje C #; desde la perspectiva del CLR, la variable es de tipo objeto). El tipo de una variable nunca cambia. El tipo de tiempo de ejecución del valor de una variable puede ser cualquier tipo compatible con el tipo de la variable. (O en el caso de los tipos de referencia, puede ser nulo.)
Eric Lippert

15
Con respecto a su segundo punto: C # ya tenía la función de "crear una variable en la que pueda poner cualquier cosa", siempre puede crear una variable de tipo objeto. Lo interesante de dinámico es lo que señala en su primer párrafo: dinámico es casi idéntico al objeto, excepto que el análisis semántico se difiere hasta el tiempo de ejecución, y el análisis semántico se realiza sobre el tipo de tiempo de ejecución de la expresión. (Mayormente. Hay algunas excepciones.)
Eric Lippert

18
He gastado un punto negativo en esto, principalmente porque está abogando implícitamente por el uso de la palabra clave para uso general. Tiene un propósito específico (descrito perfectamente en la respuesta de Lasses) y aunque esta respuesta es técnicamente correcta, es probable que extravíe a los desarrolladores.
Eight-Bit Guru

211

La dynamicpalabra 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.Applicationobjeto, 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:

  1. Argumentos opcionales
  2. Hacer refopcional para las API COM
  3. Argumentos nombrados

La 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 IDispatchreferencia.

Si no sabe qué es, IDispatches básicamente un reflejo para los objetos COM. Con una IDispatchinterfaz, 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 Invokemétodo en la IDispatchinterfaz 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 IDispatchinterfaz 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:

  1. La nueva palabra clave en C #, dynamic
  2. Un conjunto de clases de tiempo de ejecución que sabe cómo lidiar con los diferentes tipos de objetos, que implementan una API específica que dynamicrequiere 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 dynamicpalabra 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:


12
También es útil además de COM para las API JSON web donde la estructura de los objetos JSON deserializados no se especifica en C #. Por ejemplo , el método Decode de System.Web.Helpers.Json devuelve un objeto dinámico .
Dumbledad

Un comentario aparte sobre "todavía consideran a C # como un lenguaje fuertemente tipado": Eric Lippert no es fanático de "fuertemente tipado" como descripción.
Andrew Keeton el

No estoy de acuerdo con él, pero es una cuestión de opinión, no una cuestión de hecho. Para mí, "fuertemente tipado" significa que el compilador sabe, en el momento de la compilación, qué tipo se utiliza y, por lo tanto, aplica las reglas establecidas en torno a esos tipos. El hecho de que pueda optar por un tipo dinámico que posponga la comprobación de reglas y la vinculación al tiempo de ejecución no significa, para mí, que el idioma esté tipeado débilmente. Por lo general, no contraste con tipeado fuerte con tipeado débil, sin embargo, generalmente lo comparo con tipeado dinámicamente, como lenguajes como Python, donde todo es un pato hasta que ladra.
Lasse V. Karlsen

¿Cuál es el punto de esta respuesta? La mitad es sobre parámetros opcionales y la interfaz IDispatch.
Xam

Es por eso que dynamicse 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.
Lasse V. Karlsen

29

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 ischeques 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 ElevationPointes una subclase de MapPointy 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 ChartItemobjetos con atributos específicos de serialización inútiles.


No sabía sobre este caso de uso. Un poco hacky en el mejor de los casos, sin embargo. Lanzará cualquier analizador estático.
Kugel

2
@ Kugel eso es cierto, pero no lo llamaría un hack . El análisis estático es bueno, pero no dejaría que me impida una solución elegante, donde las alternativas son: violación de principio abierto-cerrado (Patrón de visitante) o mayor complejidad ciclomática con temible isapilado uno encima del otro.
Stelios Adamantidis

44
Bueno, tienes la opción de combinar patrones con C # 7, ¿no?
Kugel

2
Bueno, los operadores son mucho menos costosos de esa manera (evitando el doble lanzamiento) y obtienes un análisis estático de vuelta ;-) y el rendimiento.
Kugel

@idbrii por favor no cambies mis respuestas. Siéntase libre de dejar un comentario y aclararé (si es necesario) ya que todavía estoy activo en esta comunidad. Además, por favor no use magic; no hay tal cosa como magia.
Stelios Adamantidis

11

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.


1
Y el cambio que requirió la VM para la dinámica en realidad facilita los lenguajes dinámicos.
Dykam

2
@Dykam: No hay cambios en la VM. El DLR funciona bien de regreso a .NET 2.0.
Jörg W Mittag

@ Jörg, sí, hay un cambio. El DLR se reescribe en parte porque ahora la VM tiene soporte para la resolución dinámica.
Dykam

Era demasiado optimista, la investigación mostró que los cambios no fueron tan grandes.
Dykam

4

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;
    }

77
Pato escribiendo, bien. Sin embargo, debe usar una interfaz para esto si esos son sus tipos.
Kugel

3

Interoperabilidad COM. Especialmente I Desconocido. Fue diseñado especialmente para ello.


2

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.


Una respuesta cínica pero fácilmente demasiado cierta. Lo he visto simplemente para evitar declarar estructuras con el resultado de que el código funciona si todo está bien, pero vuela su pila de maneras impredecibles tan pronto como mueva su queso.
AnthonyVO

Sí, verás ese clásico corte de esquina con muchas otras características de lenguaje. No sorprende que también lo veas aquí.
Hawkeye4040

1

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.


77
Dudaría en decir que es "legítimo". Seguramente compilará, por lo que es un "código legítimo" en el sentido de que el compilador ahora lo compilará y el tiempo de ejecución lo ejecutará. Pero nunca quisiera ver ese código en particular (o algo parecido) en ninguno de los códigos que mantengo, o sería un delito cercano.
Lasse V. Karlsen

66
Claro, pero eso habría sido "legítimo" con "objeto" en lugar de "dinámico". No has mostrado nada interesante sobre la dinámica aquí.
Eric Lippert

Para el objeto, tendría que convertirlo al tipo apropiado, para invocar cualquiera de sus métodos ... pierde la firma; puede hacer que su código llame a cualquier método sin error de compilación, y se produce un error en tiempo de ejecución. Tenía prisa por escribir, perdón por no especificar. Y @Lasse, estaría de acuerdo y probablemente no usaré mucho la dinámica.
Brian Mains

1
El caso de uso de último recurso no se explica
denfromufa

1

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;
        }

0
  1. Puede llamar a lenguajes dinámicos como CPython usando pythonnet:

dynamic np = Py.Import("numpy")

  1. Puede emitir genéricos dynamical 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


0

Otro caso de uso para dynamicescribir es para métodos virtuales que experimentan un problema con covarianza o contravarianza. Un ejemplo de ello es el Clonemé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
    }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.