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?
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?
Risposte:
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
dynamic
in 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
). dynamic
dovrebbe 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.
dynamic
variabili 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.
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:
ref
facoltativo per le API COMLa 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 IDispatch
riferimento.
Se non sai di cosa si tratta, IDispatch
è fondamentalmente una riflessione per gli oggetti COM. Con IDispatch
un'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 Invoke
metodo IDispatch
sull'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 IDispatch
sull'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:
dynamic
dynamic
parola 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 dynamic
parola 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:
dynamic
stato 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.
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 is
controlli 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 MapPoint
e 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 ChartItem
oggetti con inutili attributi specifici di serializzazione.
is
impilati uno sopra l'altro.
magic
; non esiste una cosa come la magia.
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.
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;
}
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.
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.
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;
}
dynamic np = Py.Import("numpy")
dynamic
quando 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
Un altro caso d'uso per la dynamic
digitazione è per i metodi virtuali che riscontrano un problema con la covarianza o la contraddizione. Uno di questi esempi è il famigerato Clone
metodo 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
}
}