Para que é usado o tipo 'dinâmico' em C # 4.0?


236

O C # 4.0 introduziu um novo tipo chamado 'dinâmico'. Tudo parece bom, mas para que um programador o usaria?

Existe uma situação em que ele pode salvar o dia?



É útil ao trabalhar com COM ou idiomas de tipo dinâmico. Por exemplo, se você usasse lua ou python para criar scripts para sua linguagem, é muito conveniente chamar o código de script como se fosse um código normal.
CodesInChaos


Espero que este artigo tenha resposta completa para sua pergunta visualstudiomagazine.com/Articles/2011/02/01/…
Desenvolvedor

Respostas:


196

A palavra-chave dinâmica é nova no C # 4.0 e é usada para informar ao compilador que o tipo de uma variável pode ser alterado ou que não é conhecido até o tempo de execução. Pense nisso como sendo capaz de interagir com um Objeto sem precisar lançá-lo.

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

Observe que não precisamos lançar nem declarar cust como tipo Cliente. Como declaramos dinâmico, o tempo de execução assume o controle e, em seguida, pesquisa e define a propriedade FirstName para nós. Agora, é claro, quando você está usando uma variável dinâmica, está desistindo da verificação do tipo de compilador. Isso significa que a chamada cust.MissingMethod () será compilada e não falhará até o tempo de execução. O resultado dessa operação é uma RuntimeBinderException porque MissingMethod não está definido na classe Customer.

O exemplo acima mostra como a dinâmica funciona ao chamar métodos e propriedades. Outro recurso poderoso (e potencialmente perigoso) é poder reutilizar variáveis ​​para diferentes tipos de dados. Tenho certeza de que os programadores Python, Ruby e Perl por aí podem pensar em um milhão de maneiras de tirar proveito disso, mas uso o C # há tanto tempo que parece "errado" para mim.

dynamic foo = 123;
foo = "bar";

OK, então você provavelmente não escreverá código como o acima com muita frequência. Pode haver momentos, no entanto, em que a reutilização de variáveis ​​pode ser útil ou limpar um pedaço sujo de código legado. Um caso simples em que me deparo frequentemente é a necessidade constante de converter entre decimal e duplo.

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

A segunda linha não é compilada porque 2,5 é digitado como duplo e a linha 3 não é compilada porque Math.Sqrt espera um duplo. Obviamente, tudo o que você precisa fazer é converter e / ou alterar seu tipo de variável, mas pode haver situações em que a dinâmica faça sentido.

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");

Leia mais sobre o recurso: http://www.codeproject.com/KB/cs/CSharp4Features.aspx


97
Pessoalmente, eu não gosto do pensamento de usar o dynamicin c # para resolver problemas que podem ser resolvidos (talvez até melhor) por recursos padrão de c # e digitação estática, ou no máximo com inferência de tipo ( var). dynamic deve ser usado quando se trata de problemas de interoperabilidade com o DLR. Se você escrever código em uma linguagem estática, como c # is, faça-o e não emule uma linguagem dinâmica. Isso é apenas feio.
Philip Daubmeier

40
Se você faz uso pesado de dynamicvariáveis ​​em seu código onde não precisa delas (como no exemplo com o squareroot), desiste da verificação de erros em tempo de compilação; agora você está recebendo possíveis erros de tempo de execução.
Philip Daubmeier

33
Principalmente bem, mas alguns erros menores. Primeiro, não é correto dizer que dinâmico significa que o tipo da variável pode mudar. A variável em questão é do tipo "dinâmico" (da perspectiva da linguagem C #; da perspectiva do CLR, a variável é do tipo objeto). O tipo de uma variável nunca muda. O tipo de tempo de execução do valor de uma variável pode ser qualquer tipo compatível com o tipo da variável. (Ou, no caso de tipos de referência, que pode ser nulo.)
Eric Lippert

15
Em relação ao seu segundo ponto: o C # já tinha o recurso de "criar uma variável na qual você pode colocar qualquer coisa" - você sempre pode criar uma variável do tipo objeto. O interessante da dinâmica é o que você aponta no seu primeiro parágrafo: a dinâmica é quase idêntica ao objeto, exceto que a análise semântica é adiada até o tempo de execução, e a análise semântica é feita no tipo de expressão em tempo de execução. (. Principalmente Há algumas exceções.)
Eric Lippert

18
Eu gastei um ponto de votação negativo sobre isso, principalmente porque está implicitamente defendendo o uso da palavra-chave para uso geral. Ele tem um objetivo específico (descrito perfeitamente na resposta de Lasses) e, embora essa resposta seja tecnicamente correta, é provável que desvie os desenvolvedores.
oito bits Guru

211

A dynamicpalavra-chave foi adicionada, juntamente com muitos outros novos recursos do C # 4.0, para facilitar a conversação com código que vive ou provém de outros tempos de execução, que possui APIs diferentes.

Veja um exemplo.

Se você possui um objeto COM, como o Word.Applicationobjeto, e deseja abrir um documento, o método para fazer isso é fornecido com pelo menos 15 parâmetros, a maioria dos quais é opcional.

Para chamar esse método, você precisaria de algo assim (estou simplificando, esse não é um 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);

Observe todos esses argumentos? Você precisa passar isso desde o C # antes da versão 4.0 não ter noção de argumentos opcionais. No C # 4.0, as APIs do COM foram facilitadas de trabalhar, apresentando:

  1. Argumentos opcionais
  2. Tornando refopcional para APIs COM
  3. Argumentos nomeados

A nova sintaxe para a chamada acima seria:

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

Veja como fica mais fácil, mais legível se torna?

Vamos separar isso:

                                    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

A mágica é que o compilador C # agora injeta o código necessário e trabalha com novas classes no tempo de execução, para fazer quase exatamente a mesma coisa que você fez antes, mas a sintaxe foi oculta para você, agora você pode se concentrar no o que , e não tanto sobre como . Anders Hejlsberg gosta de dizer que você precisa invocar diferentes "encantamentos", que são uma espécie de trocadilho com a magia da coisa toda, onde você normalmente tem que agitar as mãos e dizer algumas palavras mágicas na ordem certa para conseguir um certo tipo de feitiço. A maneira antiga da API de conversar com objetos COM era muito disso; era necessário passar por muitos obstáculos para convencer o compilador a compilar o código para você.

As coisas se decompõem em C # antes da versão 4.0 ainda mais se você tentar falar com um objeto COM para o qual você não tem uma interface ou classe, tudo que você tem é uma IDispatchreferência.

Se você não sabe o que é, IDispatché basicamente uma reflexão para objetos COM. Com uma IDispatchinterface, você pode perguntar ao objeto "qual é o número de identificação do método conhecido como Salvar" e criar matrizes de um determinado tipo que contém os valores do argumento e, finalmente, chamar um Invokemétodo na IDispatchinterface para chamar o método, passando tudo as informações que você conseguiu reunir juntos.

O método Save acima pode se parecer com este (definitivamente não é o código certo):

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, ...);

Tudo isso por apenas abrir um documento.

O VB tinha argumentos opcionais e suporte para a maioria disso fora da caixa há muito tempo, portanto, este código C #:

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

é basicamente apenas C # alcançando VB em termos de expressividade, mas fazendo isso da maneira certa, tornando-o extensível, e não apenas para COM. Obviamente, isso também está disponível para o VB.NET ou qualquer outro idioma criado sobre o tempo de execução do .NET.

Você pode encontrar mais informações sobre a IDispatchinterface na Wikipedia: IDispatch, se quiser ler mais sobre ela. É uma coisa realmente sangrenta.

No entanto, e se você quisesse conversar com um objeto Python? Existe uma API diferente daquela usada para objetos COM e, como os objetos Python também são dinâmicos por natureza, você precisa recorrer à magia de reflexão para encontrar os métodos certos para chamar, seus parâmetros, etc., mas não o .NET reflexão, algo escrito para Python, praticamente como o código IDispatch acima, completamente diferente.

E para Ruby? Uma API diferente ainda.

JavaScript? O mesmo negócio, API diferente para isso também.

A palavra-chave dinâmica consiste em duas coisas:

  1. A nova palavra-chave em C #, dynamic
  2. Um conjunto de classes de tempo de execução que sabe como lidar com os diferentes tipos de objetos, que implementam uma API específica exigida pela dynamicpalavra - chave e mapeia as chamadas para a maneira correta de fazer as coisas. A API está até documentada; portanto, se você tiver objetos provenientes de um tempo de execução não coberto, poderá adicioná-lo.

A dynamicpalavra-chave não se destina, no entanto, a substituir qualquer código existente somente do .NET. Claro, você pode fazê-lo, mas não foi adicionado por esse motivo, e os autores da linguagem de programação C # com Anders Hejlsberg na frente foram os mais inflexíveis de que ainda consideram o C # como uma linguagem fortemente tipada e não sacrificam esse princípio.

Isso significa que, embora você possa escrever um código como este:

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

e compilá-lo, não era para ser uma espécie de sistema de mágica que permite descobrir o que você quis dizer em tempo de execução.

O objetivo era facilitar a conversa com outros tipos de objetos.

Há muito material na internet sobre a palavra-chave, proponentes, oponentes, discussões, reclamações, elogios etc.

Sugiro que você comece com os seguintes links e depois pesquise no Google:


12
Também é útil, além das APIs JSON do COM para web, em que a estrutura dos objetos JSON desserializados não é especificada em C #. Por exemplo , o método Decode de System.Web.Helpers.Json retorna um objeto dinâmico .
dumbledad

Além de "eles ainda consideram o C # como uma linguagem fortemente tipada": Eric Lippert não é fã de "fortemente tipada" como uma descrição.
Andrew Keeton

Eu discordo dele, mas é uma questão de opinião, não de fato. "Fortemente digitado" para mim significa que o compilador sabe, em tempo de compilação, que tipo é usado e, portanto, impõe as regras definidas em torno desses tipos. O fato de você poder optar por um tipo dinâmico que adia a verificação e a ligação de regras ao tempo de execução não significa, para mim, que o idioma seja digitado de maneira fraca. Normalmente, não contraste com tipagem forte com tipagem fraca, no entanto, costumo compará-la com tipagem dinâmica, como linguagens como Python, onde tudo é um pato até latir.
Lasse V. Karlsen

Qual é o objetivo desta resposta? Metade disso é sobre parâmetros opcionais e a interface IDispatch.
Xam

Foi por isso que dynamicfoi adicionado, para apoiar outros ecossistemas sobre como a invocação de método semelhante à reflexão pode ser feita, além de fornecer uma espécie de abordagem de caixa preta para estruturas de dados com uma maneira documentada de conseguir isso.
Lasse V. Karlsen

29

Estou surpreso que ninguém tenha mencionado vários despachos . A maneira usual de contornar isso é através do padrão Visitor e isso nem sempre é possível, então você acaba com isverificações empilhadas .

Então, aqui está um exemplo da vida real de uma aplicação minha. Em vez de fazer:

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

Você faz:

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

Observe que, no primeiro caso, ElevationPointé uma subclasse de MapPointe, se não for colocada antes MapPoint , nunca será alcançada. Esse não é o caso da dinâmica, pois o método de correspondência mais próximo será chamado.

Como você pode imaginar, a partir do código, esse recurso foi útil enquanto eu realizava a tradução dos objetos ChartItem para suas versões serializáveis. Eu não queria poluir meu código com os visitantes e também não queria poluir meus ChartItemobjetos com atributos específicos de serialização inútil.


Não sabia sobre esse caso de uso. Um pouco hacky na melhor das hipóteses, no entanto. Isso jogará fora qualquer analisador estático.
Kugel

2
@ Kugel isso é verdade, mas eu não chamaria isso de hack . A análise estática é boa, mas eu não deixaria que ela me impedisse de uma solução elegante, onde as alternativas são: violação de princípio de aberto-fechado (padrão de visitante) ou aumento da complexidade ciclomática com o temido isempilhamento um em cima do outro.
Stelios Adamantidis

4
Bem, você tem a opção de correspondência de padrões com C # 7, não?
Kugel

2
Bem, os operadores são muito mais baratos dessa maneira (evitando a conversão dupla) e você recebe a análise estática de volta ;-) e o desempenho.
Kugel

@idbrii, por favor, não mude minhas respostas. Sinta-se à vontade para enviar um comentário e esclarecerei (se necessário), pois ainda estou ativo nesta comunidade. Além disso, por favor, não use magic; não existe mágica.
Stelios Adamantidis

11

Isso facilita a interoperabilidade entre as linguagens estáticas de tipo estáticas (CLR) e as dinâmicas (python, ruby ​​...) em execução no DLR (Dynamic Language Runtime), consulte MSDN :

Por exemplo, você pode usar o código a seguir para incrementar um contador em XML em C #.

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

Usando o DLR, você poderia usar o seguinte código para a mesma operação.

scriptobj.Count += 1;

O MSDN lista estas vantagens:

  • Simplifica a portabilidade de idiomas dinâmicos para o .NET Framework
  • Habilita os recursos dinâmicos nos idiomas de tipo estaticamente
  • Fornece benefícios futuros do DLR e .NET Framework
  • Permite o compartilhamento de bibliotecas e objetos
  • Fornece expedição dinâmica rápida e invocação

Veja MSDN para mais detalhes.


1
E a mudança na VM necessária para a dinâmica realmente facilita os idiomas dinâmicos.
dykam

2
@ Tykam: Não há alterações na VM. O DLR funciona muito bem desde o .NET 2.0.
Jörg W Mittag

@ Jörg, sim, há uma mudança. O DLR é parcialmente reescrito porque agora a VM possui suporte para resolução dinâmica.
dykam

Eu estava um pouco otimista demais, a pesquisa mostrou que as mudanças não eram tão grandes.
Dykam 22/04

4

Um exemplo de uso:

Você consome muitas classes que possuem uma propriedade comum '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; }

}

Se você escrever um método commun que recupere o valor da propriedade 'CreationDate', precisará usar a reflexão:

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

Com o conceito 'dinâmico', seu código é muito mais elegante:

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

7
Digitando pato, legal. No entanto, você deve usar uma interface para isso, se esses são seus tipos.
Kugel

3

Interoperabilidade COM. Especialmente não conhecido. Foi projetado especialmente para isso.


2

Ele será usado principalmente pelas vítimas de RAD e Python para destruir a qualidade do código, o IntelliSense e a detecção de erros em tempo de compilação.


Uma resposta cínica, mas facilmente verdadeira demais. Eu já vi isso simplesmente para evitar declarar estruturas com o resultado de que o código funciona se tudo estiver bem, mas explode sua pilha de maneiras imprevisíveis assim que você move o queijo.
AnthonyVO

Sim, você verá esse canto clássico cortando com muitos outros recursos de idioma. Não é de surpreender que você também veja aqui.
precisa saber é o seguinte

1

Ele é avaliado em tempo de execução, para que você possa mudar o tipo como em JavaScript para o que quiser. Isso é legítimo:

dynamic i = 12;
i = "text";

E assim você pode alterar o tipo conforme necessário. Use-o como último recurso; é benéfico, mas ouvi muita coisa acontecendo nos bastidores em termos de IL gerada e que pode ter um preço de desempenho.


7
Eu hesitaria em dizer que é "legítimo". Ele certamente será compilado, portanto, é um "código legítimo" no sentido em que o compilador agora o compila e o tempo de execução o executa. Mas eu nunca gostaria de ver esse trecho de código específico (ou algo parecido com ele) em qualquer um dos códigos que mantenho, ou seria uma ofensa quase fatal.
Lasse V. Karlsen

6
Claro, mas isso teria sido "legítimo" com "objeto" em vez de "dinâmico". Você não mostrou nada de interessante sobre dinâmica aqui.
precisa

Para o objeto, você teria que convertê-lo no tipo apropriado, para realmente chamar qualquer um de seus métodos ... você perde a assinatura; você pode fazer com que seu código chame qualquer método sem erros de compilação e erros no tempo de execução. Estava com pressa de digitar, desculpe por não especificar. E @Lasse, eu concordo e provavelmente não usarei muito dinâmico.
Brian Mains

1
Caso Última uso resort não é explicado
denfromufa

1

O melhor caso de uso de variáveis ​​do tipo 'dinâmico' para mim foi quando, recentemente, eu estava escrevendo uma camada de acesso a dados no ADO.NET ( usando SQLDataReader ) e o código estava invocando os procedimentos armazenados herdados já gravados. Existem centenas desses procedimentos armazenados herdados que contêm grande parte da lógica de negócios. Minha camada de acesso a dados precisava retornar algum tipo de dado estruturado à camada de lógica de negócios, baseada em C #, para fazer algumas manipulações ( embora quase não existam ). Todos os procedimentos armazenados retornam um conjunto diferente de dados ( colunas da tabela ). Então, em vez de criar dezenas de classes ou estruturas para armazenar os dados retornados e passá-los para o BLL, escrevi o código abaixo, que parece bastante elegante e arrumado.

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. Você pode chamar linguagens dinâmicas como CPython usando pythonnet:

dynamic np = Py.Import("numpy")

  1. Você pode transmitir genéricos ao dynamicaplicar operadores numéricos neles. Isso fornece segurança de tipo e evita limitações de genéricos. Isso é essencialmente * digitação de pato:

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


0

Outro caso de uso para dynamicdigitação é para métodos virtuais que enfrentam um problema de covariância ou contravariância. Um exemplo é o Clonemétodo infame que retorna um objeto do mesmo tipo que o objeto em que é chamado. Esse problema não é completamente resolvido com um retorno dinâmico, porque ignora a verificação de tipo estático, mas pelo menos você não precisa usar projeções feias o tempo todo, conforme o uso simples object. Caso contrário, para dizer, os elencos se tornam 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
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.