Procedura dettagliata: creazione e utilizzo di oggetti dinamici (C# e Visual Basic)
Gli oggetti dinamici espongono membri quali proprietà e metodi in fase di esecuzione anziché in fase di compilazione. In questo modo è possibile creare oggetti che consentono di utilizzare strutture che non corrispondono a un tipo o un formato statico. Ad esempio, è possibile utilizzare un oggetto dinamico per fare riferimento al modello di oggetti documento (DOM, Document Object Model) HTML, che può contenere qualsiasi combinazione di elementi e attributi di markup HTML validi. Poiché ogni documento HTML è univoco, i membri di un determinato documento HTML vengono determinati in fase di esecuzione. Un metodo comune per fare riferimento a un attributo di un elemento HTML è passare il nome dell'attributo al metodo GetProperty dell'elemento. Per fare riferimento all'attributo id dell'elemento HTML <div id="Div1"> occorre prima ottenere un riferimento all'elemento <div> e quindi utilizzare divElement.GetProperty("id"). Se si utilizza un oggetto dinamico, è possibile fare riferimento all'attributo id con divElement.id.
Gli oggetti dinamici forniscono inoltre un pratico accesso a linguaggi dinamici quali IronPython e IronRuby. È possibile utilizzare un oggetto dinamico per fare riferimento a uno script dinamico interpretato in fase di esecuzione.
Per fare riferimento a un oggetto dinamico si utilizza l'associazione tardiva. In C# il tipo di un oggetto per cui è prevista l'associazione tardiva si specifica con dynamic. In Visual Basic il tipo di un oggetto per cui è prevista l'associazione tardiva si specifica con Object. Per ulteriori informazioni, vedere dynamic (Riferimenti per C#) e Early and Late Binding.
È possibile creare oggetti dinamici personalizzati tramite le classi nello spazio dei nomi System.Dynamic. Ad esempio, è possibile creare un oggetto ExpandoObject e specificarne i membri in fase di esecuzione. È inoltre possibile creare un tipo personalizzato che eredita la classe DynamicObject. È quindi possibile eseguire l'override dei membri della classe DynamicObject per fornire funzionalità dinamiche in fase di esecuzione.
In questa procedura dettagliata si eseguiranno le attività seguenti:
Creazione di un oggetto personalizzato che espone dinamicamente il contenuto di un file di testo come proprietà di un oggetto.
Creazione di un progetto che utilizza una libreria IronPython.
Prerequisiti
Per completare questa procedura dettagliata, è necessario disporre di IronPython 2.6.1 per .NET 4.0. È possibile scaricare IronPython 2.6.1 per .NET 4.0 da CodePlex (la pagina potrebbe essere in inglese).
Nota
Nel computer in uso è possibile che vengano visualizzati nomi o percorsi diversi per alcuni elementi dell'interfaccia utente di Visual Studio nelle istruzioni seguenti. La versione di Visual Studio in uso e le impostazioni configurate determinano questi elementi. Per ulteriori informazioni vedere Impostazioni di Visual Studio.
Creazione di un oggetto dinamico personalizzato
Il primo progetto che si crea in questa procedura dettagliata definisce un oggetto dinamico personalizzato che esegue ricerche nel contenuto di un file di testo. Il testo da cercare viene specificato tramite il nome di una proprietà dinamica. Se ad esempio nel codice chiamante si specifica dynamicFile.Sample, la classe dinamica restituisce un elenco generico di stringhe contenente tutte le righe del file che iniziano con "Sample". La ricerca non prevede la distinzione tra maiuscole e minuscole. La classe dinamica supporta altri due argomenti facoltativi. Il primo argomento è un valore di enumerazione di opzione di ricerca che specifica che la classe dinamica deve cercare corrispondenze all'inizio, alla fine o in qualsiasi punto della riga. Il secondo argomento specifica che la classe dinamica, prima di eseguire la ricerca, deve tagliare gli spazi iniziali e finali di ogni riga. Ad esempio, se nel codice chiamante si specifica dynamicFile.Sample(StringSearchOption.Contains), la classe dinamica cerca "Sample" nell'intera riga. Se nel codice chiamante si specifica dynamicFile.Sample(StringSearchOption.StartsWith, false), la classe dinamica cerca "Sample" all'inizio di ogni riga e non rimuove gli spazi iniziali e finali. Il comportamento predefinito della classe dinamica è cercare una corrispondenza all'inizio di ogni riga e rimuovere gli spazi iniziali e finali.
Per creare una classe dinamica personalizzata
Avviare Visual Studio.
Scegliere Nuovo dal menu File, quindi fare clic su Progetto.
Nel riquadro Tipi progetto della finestra di dialogo Nuovo progetto verificare che sia selezionata la voce Finestre. Selezionare Applicazione console nel riquadro Modelli. Digitare DynamicSample nella casella Nome, quindi scegliere OK. Verrà creato il nuovo progetto.
Fare clic con il pulsante destro del mouse sul progetto DynamicSample e selezionare Aggiungi, quindi scegliere Classe. Digitare ReadOnlyFile nella casella Nome, quindi scegliere OK. Verrà aggiunto un nuovo file contenente la classe ReadOnlyFile.
All'inizio del file ReadOnlyFile.cs o ReadOnlyFile.vb, aggiungere il codice seguente per importare gli spazi dei nomi System.IO e System.Dynamic.
Imports System.IO Imports System.Dynamic
using System.IO; using System.Dynamic;
L'oggetto dinamico personalizzato utilizza un'enumerazione per determinare i criteri di ricerca. Prima dell'istruzione class, aggiungere la definizione di enumerazione seguente.
Public Enum StringSearchOption StartsWith Contains EndsWith End Enum
public enum StringSearchOption { StartsWith, Contains, EndsWith }
Aggiornare l'istruzione class in modo che erediti la classe DynamicObject, come illustrato nell'esempio di codice seguente.
Public Class ReadOnlyFile Inherits DynamicObject
class ReadOnlyFile : DynamicObject
Aggiungere il codice seguente alla classe ReadOnlyFile per definire un campo privato per il percorso del file e un costruttore per la classe ReadOnlyFile.
' Store the path to the file and the initial line count value. Private p_filePath As String ' Public constructor. Verify that file exists and store the path in ' the private variable. Public Sub New(ByVal filePath As String) If Not File.Exists(filePath) Then Throw New Exception("File path does not exist.") End If p_filePath = filePath End Sub
// Store the path to the file and the initial line count value. private string p_filePath; // Public constructor. Verify that file exists and store the path in // the private variable. public ReadOnlyFile(string filePath) { if (!File.Exists(filePath)) { throw new Exception("File path does not exist."); } p_filePath = filePath; }
Aggiungere il seguente metodo GetPropertyValue alla classe ReadOnlyFile. Il metodo GetPropertyValue accetta come input i criteri di ricerca e restituisce le righe contenute in un file di testo che soddisfano tali criteri. I metodi dinamici forniti dalla classe ReadOnlyFile chiamano il metodo GetPropertyValue per recuperare i rispettivi risultati.
Public Function GetPropertyValue(ByVal propertyName As String, Optional ByVal StringSearchOption As StringSearchOption = StringSearchOption.StartsWith, Optional ByVal trimSpaces As Boolean = True) As List(Of String) Dim sr As StreamReader = Nothing Dim results As New List(Of String) Dim line = "" Dim testLine = "" Try sr = New StreamReader(p_filePath) While Not sr.EndOfStream line = sr.ReadLine() ' Perform a case-insensitive search by using the specified search options. testLine = UCase(line) If trimSpaces Then testLine = Trim(testLine) Select Case StringSearchOption Case StringSearchOption.StartsWith If testLine.StartsWith(UCase(propertyName)) Then results.Add(line) Case StringSearchOption.Contains If testLine.Contains(UCase(propertyName)) Then results.Add(line) Case StringSearchOption.EndsWith If testLine.EndsWith(UCase(propertyName)) Then results.Add(line) End Select End While Catch ' Trap any exception that occurs in reading the file and return Nothing. results = Nothing Finally If sr IsNot Nothing Then sr.Close() End Try Return results End Function
public List<string> GetPropertyValue(string propertyName, StringSearchOption StringSearchOption = StringSearchOption.StartsWith, bool trimSpaces = true) { StreamReader sr = null; List<string> results = new List<string>(); string line = ""; string testLine = ""; try { sr = new StreamReader(p_filePath); while (!sr.EndOfStream) { line = sr.ReadLine(); // Perform a case-insensitive search by using the specified search options. testLine = line.ToUpper(); if (trimSpaces) { testLine = testLine.Trim(); } switch (StringSearchOption) { case StringSearchOption.StartsWith: if (testLine.StartsWith(propertyName.ToUpper())) { results.Add(line); } break; case StringSearchOption.Contains: if (testLine.Contains(propertyName.ToUpper())) { results.Add(line); } break; case StringSearchOption.EndsWith: if (testLine.EndsWith(propertyName.ToUpper())) { results.Add(line); } break; } } } catch { // Trap any exception that occurs in reading the file and return null. results = null; } finally { if (sr != null) {sr.Close();} } return results; }
Dopo il metodo GetPropertyValue, aggiungere il codice seguente per eseguire l'override del metodo TryGetMember della classe DynamicObject. Il metodo TryGetMember viene chiamato quando viene richiesto un membro di una classe dinamica e non è specificato alcun argomento. L'argomento binder contiene informazioni sul membro a cui si fa riferimento e l'argomento result fa riferimento al risultato restituito per il membro specificato. Il metodo TryGetMember restituisce un valore booleano che restituisce true se il membro richiesto esiste. In caso contrario, restituisce false.
' Implement the TryGetMember method of the DynamicObject class for dynamic member calls. Public Overrides Function TryGetMember(ByVal binder As GetMemberBinder, ByRef result As Object) As Boolean result = GetPropertyValue(binder.Name) Return If(result Is Nothing, False, True) End Function
// Implement the TryGetMember method of the DynamicObject class for dynamic member calls. public override bool TryGetMember(GetMemberBinder binder, out object result) { result = GetPropertyValue(binder.Name); return result == null ? false : true; }
Dopo il metodo TryGetMember, aggiungere il codice seguente per eseguire l'override del metodo TryInvokeMember della classe DynamicObject. Il metodo TryInvokeMember viene chiamato quando viene richiesto un membro di una classe dinamica e si specificano argomenti. L'argomento binder contiene informazioni sul membro a cui si fa riferimento e l'argomento result fa riferimento al risultato restituito per il membro specificato. L'argomento args contiene una matrice degli argomenti passati al membro. Il metodo TryInvokeMember restituisce un valore booleano che restituisce true se il membro richiesto esiste. In caso contrario, restituisce false.
La versione personalizzata del metodo TryInvokeMember prevede che il primo argomento sia un valore ottenuto dall'enumerazione StringSearchOption definita in un passaggio precedente. Il metodo TryInvokeMember prevede che il secondo argomento sia un valore booleano. Se uno o entrambi gli argomenti sono valori validi, vengono passati al metodo GetPropertyValue per recuperare i risultati.
' Implement the TryInvokeMember method of the DynamicObject class for ' dynamic member calls that have arguments. Public Overrides Function TryInvokeMember(ByVal binder As InvokeMemberBinder, ByVal args() As Object, ByRef result As Object) As Boolean Dim StringSearchOption As StringSearchOption = StringSearchOption.StartsWith Dim trimSpaces = True Try If args.Length > 0 Then StringSearchOption = CType(args(0), StringSearchOption) Catch Throw New ArgumentException("StringSearchOption argument must be a StringSearchOption enum value.") End Try Try If args.Length > 1 Then trimSpaces = CType(args(1), Boolean) Catch Throw New ArgumentException("trimSpaces argument must be a Boolean value.") End Try result = GetPropertyValue(binder.Name, StringSearchOption, trimSpaces) Return If(result Is Nothing, False, True) End Function
// Implement the TryInvokeMember method of the DynamicObject class for // dynamic member calls that have arguments. public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { StringSearchOption StringSearchOption = StringSearchOption.StartsWith; bool trimSpaces = true; try { if (args.Length > 0) { StringSearchOption = (StringSearchOption)args[0]; } } catch { throw new ArgumentException("StringSearchOption argument must be a StringSearchOption enum value."); } try { if (args.Length > 1) { trimSpaces = (bool)args[1]; } } catch { throw new ArgumentException("trimSpaces argument must be a Boolean value."); } result = GetPropertyValue(binder.Name, StringSearchOption, trimSpaces); return result == null ? false : true; }
Salvare e chiudere il file.
Per creare un file di testo di esempio
Fare clic con il pulsante destro del mouse sul progetto DynamicSample e selezionare Aggiungi, quindi scegliere Nuovo elemento. Nel riquadro Modelli installati, selezionare Generale, quindi selezionare il modello File di testo. Non modificare il nome predefinito TextFile1.txt nella casella Nome, quindi fare clic su Aggiungi. Un nuovo file di testo verrà aggiunto al progetto.
Copiare il testo seguente nel file TextFile1.txt.
List of customers and suppliers Supplier: Lucerne Publishing (http://www.lucernepublishing.com/) Customer: Preston, Chris Customer: Hines, Patrick Customer: Cameron, Maria Supplier: Graphic Design Institute (http://www.graphicdesigninstitute.com/) Supplier: Fabrikam, Inc. (http://www.fabrikam.com/) Customer: Seubert, Roxanne Supplier: Proseware, Inc. (https://www.proseware.com/) Customer: Adolphi, Stephan Customer: Koch, Paul
Salvare e chiudere il file.
Per creare un'applicazione di esempio che utilizza l'oggetto dinamico personalizzato
In Esplora soluzioni, fare doppio clic sul file Module1.vb se si utilizza Visual Basic oppure sul file Program.cs se si utilizza Visual C#.
Aggiungere il codice seguente alla routine Main per creare un'istanza della classe ReadOnlyFile per il file TextFile1.txt. Il codice utilizza l'associazione tardiva per chiamare i membri dinamici e recuperare le righe di testo che contengono la stringa "Customer".
Dim rFile As Object = New ReadOnlyFile("..\..\TextFile1.txt") For Each line In rFile.Customer Console.WriteLine(line) Next Console.WriteLine("----------------------------") For Each line In rFile.Customer(StringSearchOption.Contains, True) Console.WriteLine(line) Next
dynamic rFile = new ReadOnlyFile(@"..\..\TextFile1.txt"); foreach (string line in rFile.Customer) { Console.WriteLine(line); } Console.WriteLine("----------------------------"); foreach (string line in rFile.Customer(StringSearchOption.Contains, true)) { Console.WriteLine(line); }
Salvare il file e premere CTRL+F5 per compilare ed eseguire l'applicazione.
Chiamata di una DLL
Il progetto successivo che si creerà in questa procedura dettagliata consentirà di accedere a una libreria scritta nel linguaggio dinamico IronPython. Prima di creare il progetto, è necessario disporre di IronPython 2.6.1 per .NET 4,0 installato. È possibile scaricare IronPython 2.6.1 per .NET 4.0 da CodePlex (la pagina potrebbe essere in inglese).
Per creare una classe dinamica personalizzata
Scegliere Nuovo dal menu File di Visual Studio, quindi fare clic su Progetto.
Nel riquadro Tipi progetto della finestra di dialogo Nuovo progetto verificare che sia selezionata la voce Finestre. Selezionare Applicazione console nel riquadro Modelli. Nella casella Nome digitare DynamicIronPythonSample, quindi fare clic su OK. Verrà creato il nuovo progetto.
Se si utilizza Visual Basic, fare clic con il pulsante destro del mouse sul progetto DynamicIronPythonSample e scegliere Proprietà. Fare clic sulla scheda Riferimenti. Fare clic sul pulsante Aggiungi. Se si utilizza Visual C#, fare clic con il pulsante destro del mouse sulla cartella Riferimenti in Esplora soluzioni, quindi scegliere Aggiungi riferimento.
Nella scheda Sfoglia passare alla cartella in cui sono installate le librerie di IronPython. Ad esempio, C:\Programmi\IronPython 2.6 per .NET 4.0. Selezionare le librerie IronPython.dll, IronPython.Modules.dll, Microsoft.Scripting.dll e Microsoft.Dynamic.dll. Fare clic su OK.
Se si utilizza Visual Basic, modificare il file Module1.vb. Se si utilizza Visual C#, modificare il file Program.cs.
All'inizio del file, aggiungere il codice seguente per importare gli spazi dei nomi Microsoft.Scripting.Hosting e IronPython.Hosting dalle librerie di IronPython.
Imports Microsoft.Scripting.Hosting Imports IronPython.Hosting
using Microsoft.Scripting.Hosting; using IronPython.Hosting;
Nel metodo principale, aggiungere il seguente codice per creare un nuovo oggetto Microsoft.Scripting.Hosting.ScriptRuntime per contenere le librerie di IronPython. L'oggetto ScriptRuntime carica il modulo di libreria random.py di IronPython.
' Set the current directory to the IronPython libraries. My.Computer.FileSystem.CurrentDirectory = My.Computer.FileSystem.SpecialDirectories.ProgramFiles & "\IronPython 2.6 for .NET 4.0\Lib" ' Create an instance of the random.py IronPython library. Console.WriteLine("Loading random.py") Dim py = Python.CreateRuntime() Dim random As Object = py.UseFile("random.py") Console.WriteLine("random.py loaded.")
// Set the current directory to the IronPython libraries. System.IO.Directory.SetCurrentDirectory( Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + @"\IronPython 2.6 for .NET 4.0\Lib"); // Create an instance of the random.py IronPython library. Console.WriteLine("Loading random.py"); ScriptRuntime py = Python.CreateRuntime(); dynamic random = py.UseFile("random.py"); Console.WriteLine("random.py loaded.");
Dopo il codice per aggiungere il modulo random.py, aggiungere il seguente codice per creare una matrice di integer. La matrice viene passata al metodo shuffle del modulo random.py, che ordina casualmente i valori nella matrice.
' Initialize an enumerable set of integers. Dim items = Enumerable.Range(1, 7).ToArray() ' Randomly shuffle the array of integers by using IronPython. For i = 0 To 4 random.shuffle(items) For Each item In items Console.WriteLine(item) Next Console.WriteLine("-------------------") Next
// Initialize an enumerable set of integers. int[] items = Enumerable.Range(1, 7).ToArray(); // Randomly shuffle the array of integers by using IronPython. for (int i = 0; i < 5; i++) { random.shuffle(items); foreach (int item in items) { Console.WriteLine(item); } Console.WriteLine("-------------------"); }
Salvare il file e premere CTRL+F5 per compilare ed eseguire l'applicazione.