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

  1. Avviare Visual Studio.

  2. Scegliere Nuovo dal menu File, quindi fare clic su Progetto.

  3. 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.

  4. 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.

  5. 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;
    
  6. 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
    }
    
  7. 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
    
  8. 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;
    }
    
  9. 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;
    }
    
  10. 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;
    }
    
  11. 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;
    }
    
  12. Salvare e chiudere il file.

Per creare un file di testo di esempio

  1. 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.

  2. 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
    
  3. Salvare e chiudere il file.

Per creare un'applicazione di esempio che utilizza l'oggetto dinamico personalizzato

  1. 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#.

  2. 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);
    }
    
  3. 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

  1. Scegliere Nuovo dal menu File di Visual Studio, quindi fare clic su Progetto.

  2. 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.

  3. 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.

  4. 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.

  5. Se si utilizza Visual Basic, modificare il file Module1.vb. Se si utilizza Visual C#, modificare il file Program.cs.

  6. 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;
    
  7. 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.");
    
  8. 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("-------------------");
    }
    
  9. Salvare il file e premere CTRL+F5 per compilare ed eseguire l'applicazione.

Vedere anche

Riferimenti

System.Dynamic

System.Dynamic.DynamicObject

dynamic (Riferimenti per C#)

Concetti

Early and Late Binding

Altre risorse

Nuove procedure dettagliate (C# e Visual Basic)