Procédure pas à pas : création et utilisation d'objets dynamiques (C# et Visual Basic)

Les objets dynamiques exposent des membres tels que les propriétés et les méthodes au moment de l'exécution et non lors de la compilation. Cela vous permet de créer des objets utilisables avec des structures qui ne correspondent pas à un type ou un format statique. Par exemple, vous pouvez utiliser un objet dynamique pour référencer le modèle DOM (Document Object Model) HTML, qui peut contenir n'importe quelle combinaison d'attributs et d'éléments de balisage HTML valides. Étant donné que chaque document HTML est unique, les membres d'un document HTML particulier sont déterminés au moment de l'exécution. L'une des méthodes courantes pour référencer un attribut d'un élément HTML consiste à passer le nom de l'attribut à la méthode GetProperty de l'élément. Pour référencer l'attribut id de l'élément HTML <div id="Div1">, vous obtenez d'abord une référence à l'élément <div>, puis utilisez divElement.GetProperty("id"). Si vous utilisez un objet dynamique, vous pouvez référencer l'attribut id comme divElement.id.

Les objets dynamiques permettent également d'accéder facilement aux langages dynamiques tels que IronPython et IronRuby. Vous pouvez utiliser un objet dynamique pour faire référence à un script dynamique qui est interprété au moment de l'exécution.

Vous référencez un objet dynamique à l'aide de la liaison tardive. En C#, vous spécifiez le type d'un objet à liaison tardive en tant que dynamic. En Visual Basic, vous spécifiez le type d'un objet à liaison tardive en tant qu'Object. Pour plus d'informations, consultez dynamic (Référence C#) et Liaison anticipée et liaison tardive (Visual Basic).

Vous pouvez créer des objets dynamiques personnalisés en utilisant les classes figurant dans l'espace de noms System.Dynamic. Par exemple, vous pouvez créer un ExpandoObject et spécifier les membres de cet objet au moment de l'exécution. Vous pouvez également créer votre propre type qui hérite de la classe DynamicObject. Vous pouvez ensuite substituer les membres de la classe DynamicObject pour fournir des fonctionnalités dynamiques au moment de l'exécution.

Dans cette procédure pas à pas, vous exécuterez les tâches suivantes :

  • Créer un objet personnalisé qui expose dynamiquement le contenu d'un fichier texte comme propriétés d'un objet.

  • Créez un projet qui utilise une bibliothèque IronPython.

Composants requis

Pour compléter cette procédure pas à pas, vous devez utiliser IronPython 2.6.1 pour .NET 4.0. Vous pouvez télécharger IronPython 2.6.1 pour le .NET Framework 4.0 sur le site CodePlex (page éventuellement en anglais).

Notes

Il est possible que votre ordinateur affiche des noms ou des emplacements différents pour certains des éléments d'interface utilisateur de Visual Studio dans les instructions suivantes. L'édition de Visual Studio dont vous disposez et les paramètres que vous utilisez déterminent ces éléments. Pour plus d'informations, consultez Paramètres Visual Studio.

Création d'un objet dynamique personnalisé

Le premier projet que vous créez dans cette procédure pas à pas définit un objet dynamique personnalisé qui recherche dans le contenu d'un fichier texte. Le texte à rechercher est spécifié par le nom d'une propriété dynamique. Par exemple, si le code appelant spécifie dynamicFile.Sample, la classe dynamique retourne une liste générique de chaînes qui contient toutes les lignes du fichier qui commencent par « Sample ». La recherche ne respecte pas la casse. La classe dynamique prend également en charge deux arguments facultatifs. Le premier argument est une valeur enum de l'option de recherche qui spécifie que la classe dynamique doit rechercher des correspondances en début de ligne, en fin de ligne, ou n'importe où dans la ligne. Le deuxième argument spécifie que la classe dynamique doit supprimer des espaces à gauche et à droite dans chaque ligne avant d'effectuer la recherche. Par exemple, si le code appelant spécifie dynamicFile.Sample(StringSearchOption.Contains), la classe dynamique recherche le terme « Sample » n'importe où dans une ligne. Si le code appelant spécifie dynamicFile.Sample(StringSearchOption.StartsWith, false), la classe dynamique recherche le terme « Sample » au début de chaque ligne et ne supprime ni les espaces à droite, ni les espaces à gauche. Le comportement par défaut de la classe dynamique consiste à rechercher une correspondance au début de chaque ligne et à supprimer les espaces à gauche et à droite.

Pour créer une classe dynamique personnalisée

  1. Démarrez Visual Studio.

  2. Dans le menu Fichier, pointez sur Nouveau, puis cliquez sur Projet.

  3. Dans la boîte de dialogue Nouveau projet, dans le volet Types de projets, vérifiez que Windows est sélectionné. Sélectionnez Application console dans le volet Modèles. Dans la zone Nom, tapez DynamicSample, puis cliquez sur OK. Le nouveau projet est créé.

  4. Cliquez avec le bouton droit sur le projet DynamicSample, pointez sur Ajouter, puis cliquez sur Classe. Dans la zone Nom, tapez ReadOnlyFile, puis cliquez sur OK. Un nouveau fichier contenant la classe ReadOnlyFile est ajouté.

  5. En haut du fichier ReadOnlyFile.cs ou ReadOnlyFile.vb, ajoutez le code suivant pour importer les espaces de noms System.Dynamic et System.IO.

    Imports System.IO
    Imports System.Dynamic
    
    using System.IO;
    using System.Dynamic;
    
  6. L'objet dynamique personnalisé utilise un enum pour déterminer les critères de recherche. Avant l'instruction de classe, ajoutez la définition d'enum suivante.

    Public Enum StringSearchOption
        StartsWith
        Contains
        EndsWith
    End Enum
    
    public enum StringSearchOption
    {
        StartsWith,
        Contains,
        EndsWith
    }
    
  7. Mettez à jour l'instruction de classe de façon à hériter de la classe DynamicObject, comme indiqué dans l'exemple de code suivant.

    Public Class ReadOnlyFile
        Inherits DynamicObject
    
    class ReadOnlyFile : DynamicObject
    
  8. Ajoutez le code suivant à la classe ReadOnlyFile afin de définir un champ privé pour le chemin d'accès au fichier et un constructeur pour 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. Ajoutez la méthode GetPropertyValue suivante à la classe ReadOnlyFile. La méthode GetPropertyValue prend les critères de recherche comme paramètre d'entrée et retourne les lignes d'un fichier texte qui correspondent aux critères de recherche. Les méthodes dynamiques fournies par la classe ReadOnlyFile appellent la méthode GetPropertyValue pour récupérer leurs résultats respectifs.

    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. Après la méthode GetPropertyValue, ajoutez le code suivant pour remplacer la méthode TryGetMember de la classe DynamicObject. La méthode TryGetMember est appelée lorsqu'un membre d'une classe dynamique est demandé et qu'aucun argument n'est spécifié. L'argument binder contient les informations relatives au membre référencé et l'argument result référence le résultat retourné pour le membre spécifié. La méthode TryGetMember retourne la valeur booléenne true si le membre demandé existe sinon, elle retourne la valeur 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. Après la méthode TryGetMember, ajoutez le code suivant pour remplacer la méthode TryInvokeMember de la classe DynamicObject. La méthode TryInvokeMember est appelée lorsqu'un membre d'une classe dynamique est demandé avec des arguments. L'argument binder contient les informations relatives au membre référencé et l'argument result référence le résultat retourné pour le membre spécifié. L'argument args contient le tableau des arguments passés au membre. La méthode TryInvokeMember retourne la valeur booléenne true si le membre demandé existe sinon, elle retourne la valeur false.

    La version personnalisée de la méthode TryInvokeMember s'attend à ce que le premier argument soit une valeur de l'enum StringSearchOption que vous avez défini dans une étape précédente. La méthode TryInvokeMember s'attend à ce que le deuxième argument soit une valeur booléenne. Si un ou les deux arguments sont des valeurs valides, ils sont passés à la méthode GetPropertyValue pour récupérer les résultats.

    ' 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. Enregistrez et fermez le fichier.

Pour créer un fichier texte d'exemple

  1. Cliquez avec le bouton droit sur le projet DynamicSample, pointez sur Ajouter, puis cliquez sur Nouvel élément. Dans le volet Modèles installés, sélectionnez Général, puis sélectionnez le modèle Fichier texte. Laissez le nom par défaut TextFile1.txt dans la zone Nom, puis cliquez Ajouter. Un nouveau fichier texte est ajouté au projet.

  2. Copiez le texte suivant dans le fichier 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. Enregistrez et fermez le fichier.

Pour créer un exemple d'application qui utilise l'objet dynamique personnalisé

  1. Dans l'Explorateur de solutions, double-cliquez sur le fichier Module1.vb si vous utilisez Visual Basic ou sur le fichier Program.cs si vous utilisez Visual C#.

  2. Ajoutez le code suivant à la procédure Main pour créer une instance de la classe ReadOnlyFile pour le fichier TextFile1.txt. Le code utilise la liaison tardive pour appeler des membres dynamiques et extraire des lignes de texte qui contiennent la chaîne "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. Enregistrez le fichier, puis appuyez sur CTRL+F5 pour générer et exécuter l'application.

Appel d'une bibliothèque dynamique de langage

Le projet suivant que vous créez dans cette procédure pas à pas permet d'accéder à une bibliothèque qui est écrite dans le langage dynamique IronPython. Avant de créer ce projet, IronPython 2.6.1 pour le .NET Framework 4.0 doit être installé. Vous pouvez télécharger IronPython 2.6.1 pour le .NET Framework 4.0 sur le site CodePlex (page éventuellement en anglais).

Pour créer une classe dynamique personnalisée

  1. Dans Visual Studio, dans le menu Fichier, pointez sur Nouveau, puis cliquez sur Projet.

  2. Dans la boîte de dialogue Nouveau projet, dans le volet Types de projets, vérifiez que Windows est sélectionné. Sélectionnez Application console dans le volet Modèles. Dans la zone Nom, tapez DynamicIronPythonSample, puis cliquez sur OK. Le nouveau projet est créé.

  3. Si vous utilisez Visual Basic, cliquez avec le bouton droit sur le projet DynamicIronPythonSample, puis cliquez sur Propriétés. Cliquez sur l'onglet Références. Cliquez sur le bouton Ajouter. Si vous utilisez Visual C#, dans l'Explorateur de solutions, cliquez avec le bouton droit sur le dossier Références, puis cliquez sur Ajouter une référence.

  4. Sous l'onglet Parcourir, naviguez jusqu'au dossier dans lequel sont installées les bibliothèques IronPython. Par exemple, C:\Program Files\IronPython 2.6 for le .NET Framework 4.0. Sélectionnez les bibliothèques IronPython.dll, IronPython.Modules.dll, Microsoft.Scripting.dll et Microsoft.Dynamic.dll. Cliquez sur OK.

  5. Si vous utilisez Visual Basic, modifiez le fichier Module1.vb. Si vous utilisez Visual C#, modifiez le fichier Program.cs.

  6. En haut du fichier, ajoutez le code suivant pour importer les espaces de noms Microsoft.Scripting.Hosting et IronPython.Hosting depuis les bibliothèques IronPython.

    Imports Microsoft.Scripting.Hosting
    Imports IronPython.Hosting
    
    using Microsoft.Scripting.Hosting;
    using IronPython.Hosting;
    
  7. Dans la méthode Main, ajoutez le code suivant pour créer un nouvel objet Microsoft.Scripting.Hosting.ScriptRuntime pour héberger les bibliothèques IronPython. L'objet ScriptRuntime charge le module de bibliothèque IronPython random.py.

    ' 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. Après que le code charge le module random.py, ajoutez le code suivant pour créer un tableau d'entiers. Le tableau est passé à la méthode shuffle du module de random.py, qui trie aléatoirement les valeurs dans le tableau.

    ' 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. Enregistrez le fichier, puis appuyez sur CTRL+F5 pour générer et exécuter l'application.

Voir aussi

Référence

System.Dynamic

System.Dynamic.DynamicObject

dynamic (Référence C#)

Concepts

Liaison anticipée et liaison tardive (Visual Basic)

Autres ressources

Nouvelles procédures pas à pas (C# et Visual Basic)