Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Informazioni sulla parola chiave dynamic in C# 4
Alexandra Rusina
La parola chiave dynamic e il DLR (Dynamic Language Runtime) sono nuove funzionalità principali di C# 4 e Microsoft .NET Framework 4 che hanno generato molto interesse e molte discussioni quando sono state annunciate. Sono state elaborate anche una vasta gamma di risposte, attualmente distribuite nella documentazione e in diversi blog e articoli tecnici. Pertanto, gli utenti continuano a porre le stesse domande nei forum e in occasione di conferenze.
In questo articolo viene fornita una panoramica generale sulle nuove funzionalità basate su parola chiave dynamic di C# 4 e vengono inoltre approfondite altre informazioni sul relativo funzionamento con altre funzionalità del linguaggio e del framework, quali variabili con reflection o tipizzate in modo implicito. Poiché sono già disponibili una notevole quantità di informazioni, riutilizzerò talvolta esempi classici con collegamenti alle fonti originali, oltre a una vasta gamma di collegamenti per approfondire la lettura.
Cosa si intende con il termine dinamico?
I linguaggi di programmazione talvolta si distinguono in linguaggi tipizzati in modo statico e in linguaggi tipizzati in modo dinamico. C# e Java vengono spesso considerati esempi di linguaggi tipizzati in modo statico, mentre Python, Ruby e JavaScript sono esempi di linguaggi tipizzati in modo dinamico.
In generale, nei linguaggi dinamici non vengono eseguiti controlli in fase di compilazione e il tipo degli oggetti viene identificato solo in fase di esecuzione. Questo approccio presenta vantaggi e svantaggi: spesso è molto più semplice e rapido scrivere il codice, ma allo stesso tempo non vengono visualizzati gli errori di compilazione ed è necessario utilizzare unit test e altre tecniche per garantire il corretto comportamento dell'applicazione.
In origine, C# è stato creato come linguaggio puramente statico, ma con l'introduzione di C# 4 sono stati aggiunti gli elementi dinamici per migliorare l'interoperabilità con i linguaggi e i framework dinamici. Il team di sviluppo di C# ha considerato diverse opzioni di progettazione, ma alla fine ha optato per l'aggiunta di una nuova parola chiave per il supporto di tali funzionalità: dynamic.
La parola chiave dynamic funge da dichiarazione di tipo statico nel sistema di gestione dei tipi di C#. In questo modo, in C# è stato possibile introdurre le funzionalità dinamiche, mantenendone le caratteristiche di linguaggio tipizzato in modo statico. Il motivo per cui e il modo in cui tale decisione è stata presa viene spiegato nella presentazione intitolata “Dynamic Binding in C# 4" di Mads Torgersen in occasione del PDC09 (microsoftpdc.com/2009/FT31). Tra le altre cose, è stato deciso che gli oggetti dinamici fossero elementi fondamentali del linguaggio C#; non è possibile attivare o disattivare le funzionalità dinamiche e a C# non sono state aggiunte funzioni simili a Option Strict On/Off di Visual Basic.
Utilizzando la parola chiave dynamic, si indica al compilatore di disattivare il controllo in fase di compilazione. Sono disponibili una vasta gamma di esempi su Internet e nella documentazione MSDN (msdn.microsoft.com/library/dd264736) su come utilizzare questa parola chiave, tra cui:
dynamic d = "test";
Console.WriteLine(d.GetType());
// Prints "System.String".
d = 100;
Console.WriteLine(d.GetType());
// Prints "System.Int32".
Come si può osservare, è possibile assegnare oggetti di tipi diversi a una variabile dichiarata come dinamica. Viene eseguita la compilazione del codice e il tipo di oggetto viene identificato in fase di esecuzione. Tuttavia, anche questo codice viene compilato in modo corretto, ma viene generata un'eccezione in fase di esecuzione:
dynamic d = "test";
// The following line throws an exception at run time.
d++;
Il motivo è lo stesso: Il compilatore non conosce il tipo di runtime dell'oggetto e, pertanto, non consente di stabilire che l'operazione di incremento non è supportata in questo caso.
L'assenza del controllo in fase di compilazione comporta anche l'assenza della funzionalità IntelliSense. Poiché il compilatore C# non conosce il tipo di oggetto, non consente ne consente l'enumerazione delle proprietà e dei metodi. Il problema potrebbe essere risolto con un'ulteriore inferenza del tipo, simile a quanto avviene negli strumenti IronPython per Visual Studio ma non per il momento in C#.
Tuttavia, in molti scenari in cui le funzionalità dinamiche si sarebbero potute rivelare utili, la funzionalità IntelliSense non era comunque disponibile perché nel codice venivano utilizzati valori letterali stringa. Il problema verrà trattato in modo più approfondito più avanti in questo articolo.
Dynamic, Object o Var?
Pertanto, qual è la reale differenza tra le parole chiave Dynamic, Object e Var e quando utilizzarle? Di seguito sono riportate brevi definizioni di ciascuna parola chiave e alcuni esempi.
La parola chiave Object rappresenta il tipo System.Object il quale è il tipo radice nella gerarchia di classi C#. La parola chiave viene spesso utilizzata quando non esiste alcun modo per identificare il tipo di oggetto in fase di compilazione, situazione che spesso si verifica in diversi scenari di interoperabilità.
È necessario utilizzare cast espliciti per convertire una variabile dichiarata come Object in un tipo specifico:
object objExample = 10;
Console.WriteLine(objExample.GetType());
In questo modo, ovviamente, viene restituito il valore del tipo System.Int32. Tuttavia, poiché il tipo statico è System.Object, è necessario utilizzare un cast esplicito nell'esempio seguente:
objExample = (int)objExample + 10;
È possibile assegnare valori di tipi diversi perché ereditano tutti dal tipo System.Object:
objExample = "test";
La parola chiave Var, a partire dalla versione C# 3.0, viene utilizzata per variabili locali tipizzate in modo implicito e per tipi anonimi. Questa parola chiave viene spesso utilizzata con LINQ. Quando una variabile viene dichiarata mediante la parola chiave Var, il tipo della variabile viene dedotto dalla stringa di inizializzazione in fase di compilazione. Il tipo della variabile non può essere modificato in fase di esecuzione. Se non è possibile dedurre il tipo, viene prodotto un errore di compilazione:
var varExample = 10;
Console.WriteLine(varExample.GetType());
In questo modo viene restituito il valore del tipo System.Int32 che corrisponde al tipo statico.
Nell'esempio seguente, non è necessario eseguire alcun cast perché il tipo statico di varExample è System.Int32:
varExample = varExample + 10;
Questa riga non viene compilata perché è possibile assegnale solo numeri interi a varExample:
varExample = "test";
La parola chiave dynamic, introdotta in C# 4, consente di scrivere e gestire in modo più semplice determinati scenari tradizionalmente basati sulla parola chiave object. In effetti, il tipo dinamico consente di utilizzare in realtà il tipo System.Object, ma a differenza di object non richiede operazioni di cast esplicite in fase di compilazione perché consente di identificare il tipo solo in fase di esecuzione:
dynamic dynamicExample = 10;
Console.WriteLine(dynamicExample.GetType());
In questo modo viene restituito il valore del tipo System.Int32.
Nella riga seguente, non è necessario eseguire alcun cast perché il tipo viene identificato solo in fase di esecuzione:
dynamicExample = dynamicExample + 10;
È possibile assegnare valori di tipi diversi a dynamicExample:
dynamicExample = "test";
È disponibile un post di blog dettagliato sulle differenze tra le parole chiave object e dynamic nel blog relativo alle Domande frequenti sul linguaggio C# (bit.ly/c95hpl).
Ciò che talvolta genera confusione è il fatto che è possibile utilizzare tutte queste parole chiave contemporaneamente senza escludersi a vicenda. Ad esempio, esaminiamo il codice seguente:
dynamic dynamicObject = new Object();
var anotherObject = dynamicObject;
Qual è il tipo di anotherObject? La risposta? dynamic. Tenere presente che dynamic è in effetti un tipo statico nel sistema di gestione dei tipi di C#, pertanto questo tipo viene dedotto per anotherObject. È importante comprendere che la parola chiave var è solo un'istruzione per consentire al compilatore di dedurre il tipo dall'espressione di inizializzazione della variabile e non un tipo.
Dynamic Language Runtime
Quando si sente il termine "dinamico" in merito al linguaggio C#, solitamente fa riferimento a uno dei due concetti: la parola chiave dynamic in C# 4 o il DLR. Benché i due concetti siano correlati, è importante comprenderne anche le differenze.
Il DLR ha due obiettivi principali. In primo luogo, consente l'interoperabilità tra linguaggi dinamici e .NET Framework. In secondo luogo, introduce il comportamento dinamico nei linguaggi C# e Visual Basic.
Il DLR è stato ideato sulla base dell'esperienza maturata durante la creazione di IronPython (ironpython.net), che è stato il primo linguaggio dinamico implementato in .NET Framework. Durante lo sviluppo di IronPython, il team ha scoperto di poter riutilizzare l'implementazione per più di un linguaggio e ha creato una piattaforma di base comune per i linguaggi dinamici .NET. Come IronPython, il DLR è diventato un progetto open source e il codice sorgente è disponibile all'indirizzo dlr.codeplex.com.
Successivamente il DLR è stato anche incluso in .NET Framework 4 per supportare le funzionalità dinamiche in C# e Visual Basic. Se è necessario utilizzare solo la parola chiave dynamic in C# 4, è sufficiente utilizzare .NET Framework che nella maggior parte dei casi consentirà di gestire autonomamente tutte le interazioni con il DLR. Tuttavia, per implementare o eseguire il porting di un nuovo linguaggio dinamico in .NET, potrebbero rivelarsi utili le classi helper aggiuntive nel progetto open source che include altri servizi e funzionalità per gli implementatori del linguaggio.
Utilizzo della parola chiave dynamic in un linguaggio tipizzato in modo statico
Tuttavia, non è necessario che tutti utilizzino la parola chiave dynamic quando possibile invece delle dichiarazioni di tipi statici. Il controllo in fase di compilazione è uno strumento potente che va sfruttato fino in fondo per quanto possibile. Ancora una volta, gli oggetti dinamici in C# non supportano la funzionalità IntelliSense, condizione che potrebbe avere delle conseguenze importanti sulla produttività complessiva.
Allo stesso tempo, esistono scenari che erano difficili da implementare in C# prima dell'introduzione della parola chiave dynamic e del DLR. Nella maggior parte dei casi, veniva utilizzato il tipo System.Object e venivano eseguiti cast espliciti senza poter sfruttare comunque i vantaggi del controllo in fase di compilazione e della funzionalità IntelliSense. Ecco alcuni esempi.
Lo scenario più famigerato è la situazione in cui è necessario utilizzare la parola chiave object per garantire l'interoperabilità con altri linguaggi o framework. Solitamente è necessario fare affidamento a un processo di reflection per stabilire il tipo dell'oggetto e per accedere alle relative proprietà e metodi. La sintassi è talvolta difficile da leggere e conseguentemente il codice è difficile da gestire. L'utilizzo della parola chiave dynamic in questo caso potrebbe rivelarsi molto più semplice e comodo rispetto al processo di reflection.
Anders Hejlsberg ha illustrato un esempio eccezionale in occasione del PDC08 (channel9.msdn.com/pdc2008/TL16) simile al seguente:
object calc = GetCalculator();
Type calcType = calc.GetType();
object res = calcType.InvokeMember(
"Add", BindingFlags.InvokeMethod,
null, new object[] { 10, 20 });
int sum = Convert.ToInt32(res);
La funzione restituisce un calcolo, ma non è noto il tipo esatto di questo oggetto di calcolo in fase di compilazione. L'unico elemento su cui è basato il codice è il fatto che l'oggetto deve includere il metodo Add. Tenere presente che la funzionalità IntelliSense è disattivata per questo metodo perché il relativo nome viene fornito come valore letterale stringa.
Con la parola chiave dynamic, il codice viene semplificato nel modo seguente:
dynamic calc = GetCalculator();
int sum = calc.Add(10, 20);
I presupposti sono gli stessi: esiste un oggetto con un tipo sconosciuto che presumibilmente include il metodo Add. Inoltre, come nell'esempio precedente, la funzionalità IntelliSense non è attivata per questo metodo. Tuttavia, la sintassi è molto più semplice da leggere e utilizzare e ha un aspetto simile alla chiamata di un tipico metodo .NET.
Contenitore di metodi dinamici
Un altro esempio in cui la parola chiave dynamic può risultare di aiuto è la creazione di contenitori di metodi dinamici, ovvero oggetti che consentono di aggiungere e rimuovere proprietà e metodi in fase di esecuzione.
The .NET Framework 4 dispone di un nuovo spazio dei nomi: System.Dynamic che è effettivamente parte del DLR. Le classi System.Dynamic.ExpandoObject e System.Expando.DynamicObject in associazione con la nuova parola chiave dynamic consente di creare strutture e gerarchie dinamiche in modo chiaro e semplice da leggere.
Ad esempio, di seguito è riportato il modo in cui è possibile aggiungere una proprietà e un metodo mediante la classe ExpandoObject:
dynamic expando = new ExpandoObject();
expando.SampleProperty =
"This property was added at run time";
expando.SampleMethod = (Action)(
() => Console.WriteLine(expando.SampleProperty));
expando.SampleMethod();
Per scenari più dettagliati, fare riferimento alla documentazione MSDN relativa alle classi ExpandoObject e DynamicObject. Inoltre, vale la pena leggere gli articoli intitolati "Dynamic Method Bags" di Bill Wagner (msdn.microsoft.com/library/ee658247) e "Dynamic in C# 4.0: Introducing the ExpandoObject" nel blog relativo alle Domande frequenti su C# (bit.ly/amRYRw).
Wrapper di classi
È possibile fornire una migliore sintassi per la propria libreria o creare un wrapper per una libreria esistente. Si tratta di uno scenario più avanzato rispetto ai due precedenti e richiede una conoscenza più approfondita delle specifiche del DLR.
Per i casi più semplici, è possibile utilizzare la classe DynamicObject. In questa classe è possibile combinare una dichiarazione statica dei metodi e delle proprietà con una funzione di invio dinamico. Pertanto, è possibile archiviare un oggetto a cui si desidera fornire una sintassi migliore in una proprietà della classe e gestire tutte le operazioni con l'oggetto tramite una funzione di invio dinamico.
Ad esempio, si osservi la classe DynamicString nella Figura 1 in cui viene eseguito il wrapping di una stringa e vengono visualizzati i nomi di tutti i metodi prima di chiamare effettivamente i metodi tramite una reflection.
Figura 1 DynamicString
public class DynamicString : DynamicObject {
string str;
public DynamicString(string str) {
this.str = str;
}
public override bool TryInvokeMember(
InvokeMemberBinder binder, object[] args,
out object result) {
Console.WriteLine("Calling method: {0}", binder.Name);
try {
result = typeof(string).InvokeMember(
binder.Name,
BindingFlags.InvokeMethod |
BindingFlags.Public |
BindingFlags.Instance,
null, str, args);
return true;
}
catch {
result = null;
return false;
}
}
}
Per creare un'istanza di questa classe, è necessario utilizzare la parola chiave dynamic:
dynamic dStr = new DynamicString("Test");
Console.WriteLine(dStr.ToUpper());
Console.ReadLine();
Ovviamente, questo particolare esempio è improbabile e non particolarmente efficiente. Tuttavia, se si dispone di un'API che dipende profondamente da una reflection, è possibile eseguire il wrapping di tutte le chiamate effettuate tramite la reflection come mostrato in questo esempio, in modo tale da non consentirne la visualizzazione da parte degli utenti finali dell'API.
Per altri esempi, consultare la documentazione MSDN (msdn.microsoft.com/library/system.dynamic.dynamicobject) e leggere il post intitolato "Dynamic in C# 4.0: Creating Wrappers with DynamicObject" nel blog relativo alle Domande frequenti su C# (bit.ly/dgS3od).
Come menzionato in precedenza, la classe DynamicObject viene fornita dal DLR. Le classi DynamicObject o ExpandoObject sono sufficienti per produrre un oggetto dinamico. Tuttavia, alcuni oggetti dinamici hanno una logica di associazione complicata per accedere ai membri o per richiamare i metodi. Per tali oggetti, è necessario implementare l'interfaccia IDynamicMetaObjectProvider e fornire la relativa funzione di invio dinamico. Questo è uno scenario avanzato e, chi fosse interessato, può leggere l'articolo intitolato "Implementing Dynamic Interfaces" di Bill Wagner (msdn.microsoft.com/vcsharp/ff800651) e "Getting Started with the DLR as a Library Author" di Alex Turner e Bill Chiles (dlr.codeplex.com).
Applicazioni gestibili tramite script
Gli script rappresentano un modo potente per fornire estendibilità alla propria applicazione. Microsoft Office rappresenta un ottimo esempio in questo caso: numerose macro, componenti aggiuntivi e plug-in esistono grazie al linguaggio VBA (Visual Basic for Applications). Inoltre, oggi il DLR consente di creare applicazioni gestibili tramite script perché fornisce un insieme comune di API hosting per i linguaggi.
Ad esempio, è possibile creare un'applicazione in cui gli utenti possano aggiungere autonomamente funzionalità senza pretendere nuove funzioni dal prodotto principali, ad esempio l'aggiunta di nuovi caratteri e mappe a un gioco o nuovi grafici a un'applicazione aziendale.
È necessario utilizzare la versione open source del DLR disponibile all'indirizzo dlr.codeplex.com invece di quella utilizzata da .NET Framework 4 perché, allo stato attuale, le API per la creazione di script e hosting del DRL sono disponibili solo nella versione open source. Inoltre, si presuppone che gli script non verranno creati in C#, ma piuttosto in uno dei linguaggi dinamici .NET, quali IronPython o IronRuby. Tuttavia, tutti i linguaggi possono supportare queste API, anche quelli non implementati basandosi sul DLR.
Per informazioni sull'utilizzo di questa funzionalità, guardare la presentazione "Using Dynamic Languages to Build Scriptable Applications" di Dino Viehland mostrata in occasione del PDC09 (microsoftpdc.com/2009/FT30).
Identificazione di oggetti dinamici
Come distinguere gli oggetti dinamici da altri oggetti? Un metodo semplice consiste nell'utilizzare le funzionalità incorporate nell'ambiente IDE. È possibile passare il puntatore del mouse sull'oggetto per verificarne il tipo di dichiarazione o controllare la disponibilità della funzionalità IntelliSense (vedere la Figura 2).
Figura 2 Oggetto dinamico in Visual Studio
In fase di esecuzione, tuttavia, la situazione diventa più complicata. Non è possibile controllare se la variabile è stata dichiarata dalla parola chiave dynamic: il tipo di runtime dell'oggetto dinamico corrisponde al tipo del valore archiviato e non è possibile ottenerne la dichiarazione del tipo statico. Si tratta di una situazione uguale alla dichiarazione della variabile come oggetto: in fase di esecuzione, è possibile ottenere solo un tipo del valore contenuto dalla variabile; non è possibile determinare se la variabile è stata originariamente dichiarata come oggetto.
In fase di esecuzione, è possibile identificare solo se un oggetto proviene dal DLR. Potrebbe essere un'informazione importante perché oggetti di tipi come ExpandoObject e DynamicObject possono modificare il proprio comportamento in fase di esecuzione; ad esempio, proprietà e metodi di aggiunta ed eliminazione.
Inoltre, non è possibile utilizzare metodi di reflection standard per ottenere informazioni relative a tali oggetti. Se si aggiunge una proprietà a un'istanza della classe ExpandoObject, non è possibile derivare questa proprietà dalla reflection:
dynamic expando = new ExpandoObject();
expando.SampleProperty = "This property was added at run time";
PropertyInfo dynamicProperty =
expando.GetType().GetProperty("SampleProperty");
// dynamicProperty is null.
L'aspetto positivo è il fatto che, in .NET Framework 4, tutti gli oggetti che consentono di aggiungere e rimuovere membri in modo dinamico devono implementare una particolare interfaccia: System.Dynamic.IDynamicMetaObjectProvider. Anche le classi DynamicObject ed ExpandoObject implementano tale interfaccia. Tuttavia, ciò non significa che qualsiasi oggetto dichiarato mediante la parola chiave dynamic implementi tale interfaccia:
dynamic expando = new ExpandoObject();
Console.WriteLine(expando is IDynamicMetaObjectProvider);
// True
dynamic test = "test";
Console.WriteLine(test is IDynamicMetaObjectProvider);
// False
Pertanto, se si utilizza la parola chiave dynamic insieme a una reflection, occorre tenere presente che la reflection non funzionerà per le proprietà e i metodi aggiunti in modo dinamico e potrebbe risultare utile verificare se l'oggetto su cui si esegue la reflection implementi l'interfaccia IDynamicMetaObjectProvider.
Parole chiave dynamic e interoperabilità COM
Lo scenario di interoperabilità COM a cui il team di sviluppo di C# si è specificamente dedicato nella versione 4 di C# è stata la programmazione all'interno delle applicazioni Microsoft Office, quali Word ed Excel. L'obiettivo era rendere questa attività più semplice e naturale possibile in C# come lo era sempre stata in Visual Basic. Ciò fa anche parte della strategia coevolutiva di Visual Basic e C#, in base alla quale entrambi i linguaggi mirano a raggiungere un'equivalenza di funzionalità in cui vengono mutuate le soluzioni migliori e più produttive in modo reciproco.
Per ulteriori informazioni, leggere l'articolo intitolato "C# and VB coevolution" nel blog su Visual Studio di Scott Wiltamuth (bit.ly/bFUpxG).
Nella Figura 3 viene visualizzato il codice C# 4 che consente di aggiungere un valore alla prima cella del foglio di lavoro di Excel e di applicare il metodo di adattamento automatico alla prima colonna. Nei commenti sotto ciascuna riga vengono mostrati gli equivalenti in C# 3.0 e versioni precedenti.
Figura 3 Creazione di script in Excel con C#
// Add this line to the beginning of the file:
// using Excel = Microsoft.Office.Interop.Excel;
var excelApp = new Excel.Application();
excelApp.Workbooks.Add();
// excelApp.Workbooks.Add(Type.Missing);
excelApp.Visible = true;
Excel.Range targetRange = excelApp.Range["A1"];
// Excel.Range targetRange = excelApp.get_Range("A1", Type.Missing);
targetRange.Value = "Name";
// targetRange.set_Value(Type.Missing, "Name");
targetRange.Columns[1].AutoFit();
// ((Excel.Range)targetRange.Columns[1, Type.Missing]).AutoFit();
L'aspetto interessante di questo esempio è il fatto che non è possibile visualizzare la parola chiave dynamic in alcuna posizione del codice. In effetti, viene utilizzata solo in una riga:
targetRange.Columns[1].AutoFit();
// ((Excel.Range)targetRange.Columns[1, Type.Missing]).AutoFit();
Nella versione C# 3.0, la riga targetRange.Columns[1, Type.Missing] restituisce l'oggetto e questo è il motivo per cui è necessaria l'esecuzione del cast a Excel.Range. Tuttavia, in C# 4 e Visual Studio 2010 tali chiamate vengono automaticamente convertite in dinamiche. Pertanto, il tipo dell'oggetto targetRange.Columns[1] in C# 4 è in realtà dinamico.
Un altro aspetto interessante è il fatto che i miglioramenti dell'interoperabilità COM in C# 4 non si limitano all'introduzione della parola chiave dynamic. In tutte le altre righe si ottiene una migliore sintassi grazie a altre nuove funzionalità, quali proprietà indicizzate nonché parametri denominati e facoltativi. Un'ottima introduzione di queste nuove funzionalità è disponibile nell'articolo di MSDN Magazine intitolato "New C# Features in the .NET Framework 4" di Chris Burrows (msdn.microsoft.com/magazine/ff796223).
Dove è possibile trovare ulteriori informazioni?
Mi auguro che il presente articolo abbia risposto alla maggior parte delle domande relative alla parola chiave dynamic in C# 4, ma non credo. Per commenti, domande o suggerimenti, visitare il sito Web all'indirizzo dlr.codeplex.com/discussions e porre eventuali domande che potrebbero essere già state poste da altri utenti oppure creare una nuova discussione. Abbiamo una community attiva che accoglie positivamente nuovi membri.
Alexandra Rusina è Program Manager nel team di Silverlight. Prima di tale incarico, ha lavorato come programmatrice nel team linguistico di Visual Studio durante lo sviluppo di Visual Studio 2010. Ha inoltre regolarmente pubblicato post nel blog relativo alle Domande frequenti su C# (blogs.msdn.com/b/csharpfaq/).
Un ringraziamento al seguente esperto tecnico per la revisione dell'articolo: Bill Chiles