A cosa serve il tipo 'dinamico' in C # 4.0?


236

C # 4.0 ha introdotto un nuovo tipo chiamato "dinamico". Sembra tutto a posto, ma per cosa lo userebbe un programmatore?

C'è una situazione in cui può salvare la giornata?



È utile quando si lavora con COM o linguaggi tipizzati dinamicamente. Ad esempio, se utilizzassi lua o python per creare script nella tua lingua, è molto comodo chiamare semplicemente il codice di scripting come se fosse un codice normale.
Codici A Caos il


Spero che questo articolo abbia una risposta completa alla tua domanda visualstudiomagazine.com/Articles/2011/02/01/…
Sviluppatore

Risposte:


196

La parola chiave dinamica è una novità di C # 4.0 e viene utilizzata per indicare al compilatore che il tipo di una variabile può cambiare o che non è noto fino al runtime. Pensalo come se fosse in grado di interagire con un Oggetto senza doverlo lanciare.

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

Si noti che non è stato necessario trasmettere o dichiarare la cust come tipo di cliente. Poiché l'abbiamo dichiarato dinamico, il runtime prende il sopravvento, quindi cerca e imposta la proprietà FirstName per noi. Ora, ovviamente, quando si utilizza una variabile dinamica, si rinuncia al controllo del tipo di compilatore. Ciò significa che la chiamata cust.MissingMethod () verrà compilata e non avrà esito negativo fino al runtime. Il risultato di questa operazione è una RuntimeBinderException perché MissingMethod non è definito nella classe Customer.

L'esempio sopra mostra come funziona la dinamica quando si chiamano metodi e proprietà. Un'altra funzionalità potente (e potenzialmente pericolosa) è la possibilità di riutilizzare le variabili per diversi tipi di dati. Sono sicuro che i programmatori Python, Ruby e Perl là fuori possano pensare a un milione di modi per trarne vantaggio, ma sto usando C # da così tanto tempo che mi sembra "sbagliato".

dynamic foo = 123;
foo = "bar";

OK, quindi molto probabilmente non scriverai codice come sopra molto spesso. In alcuni casi, tuttavia, può essere utile riutilizzare le variabili o ripulire un pezzo di codice legacy sporco. Un caso semplice in cui mi imbatto spesso è di dover costantemente scegliere tra decimale e doppio.

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

La seconda riga non viene compilata perché 2.5 viene digitato come doppio e la riga 3 non viene compilata perché Math.Sqrt prevede un doppio. Ovviamente, tutto ciò che devi fare è trasmettere e / o modificare il tipo di variabile, ma potrebbero esserci situazioni in cui la dinamica ha senso usare.

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

Altre informazioni: http://www.codeproject.com/KB/cs/CSharp4Features.aspx


97
Personalmente non mi piace il pensiero di usare in dynamicin c # per risolvere problemi che possono essere risolti (forse anche meglio) dalle funzionalità standard di c # e dalla tipizzazione statica, o al massimo con l'inferenza del tipo ( var). dynamicdovrebbe essere usato solo quando si tratta di problemi di interoperabilità con il DLR. Se scrivi il codice in un linguaggio tipizzato statico, come è c #, allora fallo e non emulare un linguaggio dinamico. È semplicemente brutto.
Philip Daubmeier,

40
Se usi pesantemente le dynamicvariabili nel tuo codice dove non ne hai bisogno (come nel tuo esempio con lo squareroot), rinunci al controllo degli errori durante la compilazione; invece ora stai ottenendo possibili errori di runtime.
Philip Daubmeier,

33
Per lo più bene, ma un paio di errori minori. Innanzitutto, non è corretto affermare che dinamico significa che il tipo di variabile può cambiare. La variabile in questione è di tipo "dinamico" (dal punto di vista del linguaggio C #; dal punto di vista del CLR la variabile è di tipo oggetto). Il tipo di una variabile non cambia mai . Il tipo di runtime del valore di una variabile può essere di qualsiasi tipo compatibile con il tipo di variabile. (O nel caso di tipi di riferimento, può essere nullo.)
Eric Lippert

15
Per quanto riguarda il secondo punto: C # aveva già la funzione di "crea una variabile in cui puoi inserire qualsiasi cosa": puoi sempre creare una variabile di tipo oggetto. La cosa interessante della dinamica è ciò che metti in evidenza nel tuo primo paragrafo: la dinamica è quasi identica all'oggetto, tranne per il fatto che l'analisi semantica viene rinviata al runtime e l'analisi semantica viene eseguita sul tipo di runtime dell'espressione. (Principalmente. Ci sono alcune eccezioni.)
Eric Lippert,

18
Ho speso un punto negativo su questo, principalmente perché sta implicitamente sostenendo l'uso della parola chiave per uso generale. Ha uno scopo specificamente mirato (descritto perfettamente nella risposta di Lasses) e sebbene questa risposta sia tecnicamente corretta, è probabile che porti fuori strada gli sviluppatori.
Guru a 8 bit

211

Il dynamic parola chiave è stata aggiunta, insieme a molte altre nuove funzionalità di C # 4.0, per semplificare la conversazione con il codice che vive o proviene da altri runtime, con API diverse.

Fai un esempio.

Se hai un oggetto COM, come il Word.Application oggetto, e si desidera aprire un documento, il metodo per farlo prevede non meno di 15 parametri, molti dei quali sono opzionali.

Per chiamare questo metodo, avresti bisogno di qualcosa del genere (sto semplificando, questo non è un codice reale):

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

Nota tutti questi argomenti? È necessario passare quelli poiché C # prima della versione 4.0 non aveva una nozione di argomenti opzionali. In C # 4.0, le API COM sono state semplificate con l'introduzione di:

  1. Argomenti opzionali
  2. Rendere reffacoltativo per le API COM
  3. Argomenti nominati

La nuova sintassi per la chiamata precedente sarebbe:

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

Vedi quanto sembra più facile, quanto diventa più leggibile?

Dividiamolo:

                                    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 è che il compilatore C # ora inietterà il codice necessario e lavorerà con nuove classi in runtime, per fare quasi la stessa cosa che hai fatto prima, ma la sintassi è stata nascosta da te, ora puoi concentrarti sul cosa , e non tanto sul come . Anders Hejlsberg ama dire che devi invocare diversi "incantesimi", che è una sorta di gioco di parole sulla magia di tutto, in cui in genere devi agitare le mani e dire alcune parole magiche nell'ordine giusto per far andare un certo tipo di incantesimo. Il vecchio modo API di parlare con gli oggetti COM era molto di questo, era necessario saltare attraverso molti cerchi per convincere il compilatore a compilare il codice per te.

Le cose si rompono in C # prima della versione 4.0 ancora di più se provi a parlare con un oggetto COM per il quale non hai un'interfaccia o una classe, tutto ciò che hai è un IDispatchriferimento.

Se non sai di cosa si tratta, IDispatchè fondamentalmente una riflessione per gli oggetti COM. Con IDispatchun'interfaccia puoi chiedere all'oggetto "qual è il numero ID per il metodo noto come Salva", e costruire array di un certo tipo contenente i valori dell'argomento e infine chiamare un Invokemetodo IDispatchsull'interfaccia per chiamare il metodo, passando tutto le informazioni che sei riuscito a trascinare insieme.

Il metodo Save sopra potrebbe apparire così (questo non è sicuramente il codice giusto):

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

Tutto questo solo per l'apertura di un documento.

VB aveva argomenti e supporto opzionali per la maggior parte di questo fuori dalla scatola molto tempo fa, quindi questo codice C #:

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

fondamentalmente è solo C # che raggiunge VB in termini di espressività, ma lo fa nel modo giusto, rendendolo estendibile, e non solo per COM. Ovviamente questo è disponibile anche per VB.NET o qualsiasi altra lingua costruita sul runtime .NET.

Puoi trovare maggiori informazioni IDispatchsull'interfaccia su Wikipedia: IDispatch se vuoi saperne di più. È roba davvero cruenta.

Tuttavia, se volessi parlare con un oggetto Python? Esiste un'API diversa da quella utilizzata per gli oggetti COM e, poiché anche gli oggetti Python sono di natura dinamica, è necessario ricorrere alla magia della riflessione per trovare i metodi giusti da chiamare, i loro parametri, ecc., Ma non .NET. riflessione, qualcosa di scritto per Python, un po 'come il codice IDispatch sopra, completamente diverso.

E per Ruby? Un'API diversa ancora.

JavaScript? Stesso affare, API diverse anche per quello.

La parola chiave dinamica consiste di due cose:

  1. La nuova parola chiave in C #, dynamic
  2. Un insieme di classi di runtime che sa come gestire i diversi tipi di oggetti, che implementano un'API specifica richiesta dalla dynamicparola chiave e associa le chiamate al modo giusto di fare le cose. L'API è persino documentata, quindi se hai oggetti che provengono da un runtime non coperto, puoi aggiungerlo.

La dynamicparola chiave, tuttavia, non intende sostituire alcun codice esistente solo per .NET. Certo, puoi farlo, ma non è stato aggiunto per questo motivo, e gli autori del linguaggio di programmazione C # con Anders Hejlsberg nella parte anteriore, è stato fermamente convinto che considerano ancora C # come un linguaggio fortemente tipizzato e non sacrificheranno quel principio.

Ciò significa che sebbene sia possibile scrivere codice in questo modo:

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

e averlo compilato, non era inteso come una sorta di sistema magico-lascia-capire-cosa-intendevi-al-runtime.

Lo scopo era quello di rendere più facile parlare con altri tipi di oggetti.

C'è un sacco di materiale su Internet sulla parola chiave, i sostenitori, gli oppositori, le discussioni, gli sfoghi, gli elogi, ecc.

Ti suggerisco di iniziare con i seguenti link e poi google per ulteriori informazioni:


12
È inoltre utile a parte COM per le API JSON Web in cui la struttura degli oggetti JSON non serializzati non è specificata in C #. Ad esempio il metodo Decode di System.Web.Helpers.Json restituisce un oggetto dinamico .
Silente,

A parte "considerano ancora C # come un linguaggio fortemente tipizzato": Eric Lippert non è un fan di "fortemente tipizzato" come una descrizione.
Andrew Keeton,

Non sono d'accordo con lui, ma è una questione di opinione, non di fatto. Per me "fortemente tipizzato" significa che il compilatore conosce, al momento della compilazione, quale tipo viene utilizzato, e quindi applica le regole impostate attorno a quei tipi. Il fatto che sia possibile optare per un tipo dinamico che rimanda la verifica delle regole e l'associazione al runtime non significa, per me, che la lingua sia tipizzata debolmente. Di solito non contrappongo i caratteri fortemente tipizzati a quelli deboli, tuttavia, di solito li paragono a quelli dinamicamente, come i linguaggi come Python, dove tutto è un papero fino a quando non abbaia.
Lasse V. Karlsen,

Qual è il punto di questa risposta? La metà riguarda parametri opzionali e l'interfaccia IDispatch.
X

Questo è il motivo per cui è dynamicstato aggiunto, per supportare altri ecosistemi su come si può fare l'invocazione di un metodo simile alla riflessione, oltre a fornire una sorta di approccio black box alle strutture di dati con un modo documentato per raggiungere questo obiettivo.
Lasse V. Karlsen,

29

Sono sorpreso che nessuno abbia menzionato la spedizione multipla . Il solito modo per aggirare questo problema è tramite il modello Visitatore e ciò non è sempre possibile, quindi si finisce con iscontrolli in pila .

Quindi, ecco un esempio di vita reale di una mia applicazione. Invece di fare:

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

Tu fai:

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

Si noti che nel primo caso ElevationPointè una sottoclasse di MapPointe se non viene posizionata prima MapPoint non verrà mai raggiunta. Questo non è il caso della dinamica, poiché verrà chiamato il metodo di corrispondenza più vicino.

Come puoi immaginare dal codice, quella funzionalità mi è stata utile mentre eseguivo la traduzione dagli oggetti ChartItem alle loro versioni serializzabili. Non volevo inquinare il mio codice con i visitatori e non volevo anche inquinare i miei ChartItemoggetti con inutili attributi specifici di serializzazione.


Non sapevo di questo caso d'uso. Un po 'confuso, nella migliore delle ipotesi, però. Eliminerà qualsiasi analizzatore statico.
Kugel,

2
@Kugel è vero, ma non lo definirei un hack . L'analisi statica è buona, ma non vorrei che mi impedisse di trovare una soluzione elegante, in cui le alternative sono: violazione del principio aperto-chiuso (modello Visitatore) o aumento della complessità ciclomatica con temuti isimpilati uno sopra l'altro.
Stelios Adamantidis,

4
Bene, hai la possibilità di abbinare il motivo con C # 7, no?
Kugel,

2
Bene, gli operatori sono molto meno costosi in questo modo (evitando il doppio cast) e si ottiene indietro l'analisi statica ;-) e le prestazioni.
Kugel,

@idbrii per favore non cambiare le mie risposte. Sentiti libero di lasciare un commento e chiarirò (se necessario) poiché sono ancora attivo in questa comunità. Inoltre, per favore non usare magic; non esiste una cosa come la magia.
Stelios Adamantidis,

11

Semplifica l'interazione tra i linguaggi di tipo statico (CLR) e quelli dinamici (python, ruby ​​...) in esecuzione sul DLR (runtime di linguaggio dinamico), vedere MSDN :

Ad esempio, è possibile utilizzare il seguente codice per incrementare un contatore in XML in C #.

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

Utilizzando il DLR, è possibile utilizzare il seguente codice invece per la stessa operazione.

scriptobj.Count += 1;

MSDN elenca questi vantaggi:

  • Semplifica il porting dei linguaggi dinamici su .NET Framework
  • Abilita le funzionalità dinamiche in linguaggi tipicamente statici
  • Fornisce i vantaggi futuri di DLR e .NET Framework
  • Abilita la condivisione di librerie e oggetti
  • Fornisce invio dinamico veloce e invocazione

Vedi MSDN per maggiori dettagli.


1
E il cambiamento richiesto dalla VM per la dinamica in realtà semplifica le lingue dinamiche.
Dykam,

2
@Dykam: la VM non è stata modificata. Il DLR funziona perfettamente su .NET 2.0.
Jörg W Mittag,

@Jörg, sì, c'è un cambiamento. Il DLR viene parzialmente riscritto perché ora la VM ha integrato il supporto per la risoluzione dinamica.
Dykam,

Ero un po 'troppo ottimista, la ricerca ha mostrato che i cambiamenti non erano così grandi.
Dykam,

4

Un esempio di utilizzo:

Consumi molte classi che hanno una proprietà comune '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 scrivi un metodo commun che recupera il valore della proprietà 'CreationDate', dovresti usare la riflessione:

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

Con il concetto "dinamico", il tuo codice è molto più elegante:

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

7
Duck digitando, bello. Tuttavia, è necessario utilizzare un'interfaccia per questo se quelli sono i tuoi tipi.
Kugel,

3

Interoperabilità COM Soprattutto IUnnown. È stato progettato appositamente per questo.


2

Sarà utilizzato principalmente dalle vittime di RAD e Python per distruggere la qualità del codice, IntelliSense e il rilevamento dei bug in fase di compilazione.


Una risposta cinica ma facilmente troppo vera. L'ho visto fatto semplicemente per evitare di dichiarare le strutture con il risultato che il codice funziona se tutto va bene, ma fa esplodere il suo stack in modi imprevedibili non appena si sposta il formaggio.
AnthonyVO,

Sì, vedrai quel classico taglio d'angolo con molte altre funzionalità linguistiche. Non sorprende che lo vedresti anche qui.
Hawkeye4040,

1

Valuta in fase di esecuzione, quindi puoi cambiare il tipo come puoi in JavaScript con quello che vuoi. Questo è legittimo:

dynamic i = 12;
i = "text";

E così puoi cambiare il tipo di cui hai bisogno. Usalo come ultima risorsa; è vantaggioso, ma ho sentito molto succedere sotto le scene in termini di IL generato e che può avere un prezzo di prestazione.


7
Sarei titubante nel dire che è "legittimo". Verrà sicuramente compilato, quindi come tale è "codice legittimo", nel senso che il compilatore ora lo compilerà e il runtime lo eseguirà. Ma non vorrei mai vedere quel particolare pezzo di codice (o qualcosa di simile a esso) in uno qualsiasi dei codici che mantengo, o sarebbe un'offesa quasi istantanea.
Lasse V. Karlsen,

6
Certo, ma sarebbe stato "legittimo" con "oggetto" anziché "dinamico". Non hai mostrato nulla di interessante sulla dinamica qui.
Eric Lippert,

Per oggetto, dovresti lanciarlo nel tipo appropriato, al fine di invocare effettivamente uno qualsiasi dei suoi metodi ... perdi la firma; puoi fare in modo che il tuo codice chiami qualsiasi metodo senza errori di compilazione ed errori in fase di esecuzione. Avevo fretta di scrivere, mi spiace non aver specificato. E @Lasse, sarei d'accordo e probabilmente non userò molto la dinamica.
Brian Mains,

1
Il caso d'uso dell'ultimo ricorso non è spiegato
denfromufa

1

Il miglior caso d'uso di variabili di tipo "dinamico" per me è stato quando, di recente, stavo scrivendo un livello di accesso ai dati in ADO.NET ( usando SQLDataReader ) e il codice stava invocando le procedure memorizzate legacy già scritte. Esistono centinaia di quelle stored procedure legacy che contengono la maggior parte della logica aziendale. Il mio livello di accesso ai dati doveva restituire una sorta di dati strutturati al livello di logica aziendale, basato su C #, per eseguire alcune manipolazioni ( sebbene non ce ne siano quasi ). Ogni procedura memorizzata restituisce diversi set di dati ( colonne della tabella ). Quindi, invece di creare dozzine di classi o strutture per contenere i dati restituiti e passarli al BLL, ho scritto il codice seguente che sembra abbastanza elegante e pulito.

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. Puoi chiamare in linguaggi dinamici come CPython usando pythonnet:

dynamic np = Py.Import("numpy")

  1. È possibile eseguire il cast di generici dynamicquando si applicano operatori numerici su di essi. Ciò fornisce la sicurezza del tipo ed evita le limitazioni dei generici. Questo è in sostanza * anatra digitando:

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


0

Un altro caso d'uso per la dynamicdigitazione è per i metodi virtuali che riscontrano un problema con la covarianza o la contraddizione. Uno di questi esempi è il famigerato Clonemetodo che restituisce un oggetto dello stesso tipo dell'oggetto su cui viene chiamato. Questo problema non è completamente risolto con un ritorno dinamico perché ignora il controllo statico del tipo, ma almeno non è necessario utilizzare brutti cast tutto il tempo come quando si usa plain object. Altrimenti, i cast diventano impliciti.

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
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.