Tutorial: Crear y utilizar objetos dinámicos (C# y Visual Basic)

Los objetos dinámicos exponen miembros como propiedades y métodos en tiempo de ejecución, en lugar de en tiempo de compilación. Esto permite crear objetos para trabajar con estructuras que no coinciden con un tipo o formato estático. Por ejemplo, puede utilizar un objeto dinámico para hacer referencia al Modelo de objeto del documento HTML (DOM), que puede contener cualquier combinación de elementos y atributos HTML válidos. Dado que cada documento HTML es único, se determinan los miembros del documento en tiempo de ejecución. Un método habitual de hacer referencia a un atributo de un elemento XHTML consiste en pasar el nombre del atributo al método GetProperty del elemento. Para hacer referencia al atributo id del elemento HTML <div id="Div1">, primero hay que obtener una referencia al elemento <div> y, a continuación, utilizar divElement.GetProperty("id"). Si utiliza un objeto dinámico, puede hacer referencia al atributo id como divElement.id.

Los objetos dinámicos también proporcionan acceso más cómodo a los lenguajes dinámicos, como IronPython e IronRuby. Puede usar un objeto dinámico para hacer referencia a un script dinámico que se interpreta en tiempo de ejecución.

Para hacer referencia a un objeto dinámico se puede usar el enlace en tiempo de ejecución. En C#, se especifica el tipo de un objeto enlazado en tiempo de ejecución como dynamic. En Visual Basic, se especifica el tipo de un objeto enlazado en tiempo de ejecución como Object. Para obtener más información, vea dynamic (Referencia de C#) y Enlace en tiempo de compilación y en tiempo de ejecución (Visual Basic).

Puede crear objetos dinámicos personalizados utilizando las clases del espacio de nombres System.Dynamic. Por ejemplo, puede crear ExpandoObject y especificar los miembros de ese objeto en tiempo de ejecución. También puede crear un tipo que herede la clase DynamicObject. Después puede reemplazar los miembros de la clase DynamicObject para proporcionar la funcionalidad dinámica en tiempo de ejecución.

En este tutorial realizará las tareas siguientes:

  • Crear un objeto personalizado que expone dinámicamente al contenido de un archivo de texto como propiedades de un objeto.

  • Cree un proyecto que use una biblioteca de IronPython.

Requisitos previos

Para poder completar este tutorial, necesita IronPython 2.6.1 para .NET Framework 4. Puede descargar IronPython 2.6.1 para .NET Framework 4 de CodePlex.

Nota

Es posible que su equipo muestre nombres o ubicaciones diferentes para algunos de los elementos de la interfaz de usuario de Visual Studio incluidos en las instrucciones siguientes. La edición de Visual Studio que se tenga y la configuración que se utilice determinan estos elementos. Para obtener más información, vea Valores de configuración de Visual Studio.

Crear un objeto dinámico personalizado

En el primer proyecto que se crea en este tutorial se define un objeto dinámico personalizado que busca en el contenido de un archivo de texto. El nombre de una propiedad dinámica especifica el texto que hay que buscar. Por ejemplo, si el código de llamada especifica dynamicFile.Sample, la clase dinámica devuelve una lista genérica de cadenas que contiene todas las líneas del archivo que comienzan con "Sample". La búsqueda no distingue entre mayúsculas y minúsculas. La clase dinámica también admite dos argumentos opcionales. El primer argumento es un valor enum de la opción de búsqueda que especifica que la clase dinámica debería buscar las coincidencias en el inicio, en el final o en cualquier parte de la línea. El segundo argumento especifica que la clase dinámica debería recortar los espacios iniciales y finales de cada línea antes de buscar. Por ejemplo, si el código de llamada especifica dynamicFile.Sample(StringSearchOption.Contains), la clase dinámica busca "Sample" en cualquier parte de una línea. Si el código de llamada especifica dynamicFile.Sample(StringSearchOption.StartsWith, false), la clase dinámica busca "Sample" en el inicio de cada línea y no quita los espacios iniciales y finales. El comportamiento predeterminado de la clase dinámica es buscar una coincidencia en el inicio de cada línea y quitar los espacios iniciales y finales.

Para crear una clase dinámica personalizada

  1. Inicie Visual Studio.

  2. En el menú Archivo, elija Nuevo y haga clic en Proyecto.

  3. En el cuadro de diálogo Nuevo proyecto, en el recuadro Tipos de proyecto, asegúrese de haber seleccionado Windows. En el recuadro Plantillas, seleccione Aplicación de consola. En el cuadro Nombre, escriba DynamicSample y, a continuación, haga clic en Aceptar. Se crea el nuevo proyecto.

  4. Haga clic con el botón secundario en el proyecto DynamicSample, señale Agregar y haga clic en Clase. En el cuadro Nombre, escriba ReadOnlyFile y, a continuación, haga clic en Aceptar. Se agrega un nuevo archivo que contiene la clase ReadOnlyFile.

  5. En la parte superior del archivo ReadOnlyFile.cs o ReadOnlyFile.vb, agregue el siguiente código para importar los espacios de nombres System.Dynamic y System.IO.

    Imports System.IO
    Imports System.Dynamic
    
    using System.IO;
    using System.Dynamic;
    
  6. El objeto dinámico personalizado utiliza una enumeración para determinar el criterio de búsqueda. Antes de la instrucción de clase, agregue la siguiente definición de enumeración.

    Public Enum StringSearchOption
        StartsWith
        Contains
        EndsWith
    End Enum
    
    public enum StringSearchOption
    {
        StartsWith,
        Contains,
        EndsWith
    }
    
  7. Actualice la instrucción de clase que herede la clase DynamicObject, como se muestra en el siguiente ejemplo de código.

    Public Class ReadOnlyFile
        Inherits DynamicObject
    
    class ReadOnlyFile : DynamicObject
    
  8. Agregue el código siguiente a la clase ReadOnlyFile para definir un campo privado para la ruta de archivo y un constructor para la clase 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. Agregue el método GetPropertyValue a la clase ReadOnlyFile. El método GetPropertyValue toma como entrada los criterios de búsqueda y devuelve las líneas de un archivo de texto que coinciden con el criterio de búsqueda. Los métodos dinámicos proporcionados por la clase ReadOnlyFile llaman al método GetPropertyValue para recuperar los resultados respectivos.

    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. Después del método GetPropertyValue, agregue el código siguiente para reemplazar el método TryGetMember de la clase DynamicObject. Se llama al método TryGetMember cuando se solicita un miembro de una clase dinámica y no se especifica ningún argumento. El argumento binder contiene información sobre el miembro al que se ha hecho referencia y el argumento result hace referencia al resultado devuelto para el miembro especificado. El método TryGetMember devuelve un valor booleano que devuelve true si el miembro solicitado existe; de lo contrario devuelve 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. Después del método TryGetMember, agregue el código siguiente para reemplazar el método TryInvokeMember de la clase DynamicObject. Se llama al método TryInvokeMember cuando un miembro de una clase dinámica se solicita con argumentos. El argumento binder contiene información sobre el miembro al que se ha hecho referencia y el argumento result hace referencia al resultado devuelto para el miembro especificado. El argumento args contiene una matriz de argumentos que se pasan al miembro. El método TryInvokeMember devuelve un valor booleano que devuelve true si el miembro solicitado existe; de lo contrario devuelve false.

    La versión personalizada del método TryInvokeMember espera el que primer argumento sea un valor de la enumeración StringSearchOption que definió en un paso anterior. El método TryInvokeMember espera que el segundo argumento sea un valor booleano. Si uno o ambos argumentos son valores válidos, se pasan al método GetPropertyValue para recuperar los resultados.

    ' 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. Guarde y cierre el archivo.

Para crear un archivo de texto de muestra

  1. Haga clic con el botón secundario en el proyecto DynamicSample, señale Agregar y haga clic en Nuevo elemento. En el recuadro Plantillas instaladas, seleccione General y, a continuación, seleccione la plantilla Archivo de texto. Deje el nombre predeterminado de TextFile1.txt en el cuadro Nombre y haga clic en Agregar. Se agregará un nuevo archivo de texto al proyecto.

  2. Copie el siguiente texto en el archivo 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. Guarde y cierre el archivo.

Para crear una aplicación de ejemplo que utiliza el objeto dinámico personalizado

  1. En el Explorador de soluciones, haga doble clic en el archivo Module1.vb si está utilizando Visual Basic o el archivo Program.cs si utiliza Visual C#.

  2. Agregue el siguiente código al procedimiento Main para crear una instancia de la clase ReadOnlyFile para el archivo TextFile1.txt. El código utiliza el enlace en tiempo de ejecución para llamar a los miembros dinámicos y recuperar líneas de texto que contienen la cadena "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. Guarde el archivo y presione CTRL+F5 para compilar y ejecutar la aplicación.

Llamar a una biblioteca de lenguaje dinámico

El proyecto siguiente que se crea en este tutorial obtiene acceso a una biblioteca escrita en el lenguaje dinámico IronPython. Para crear este proyecto, debe tener instalado IronPython 2.6.1 para .NET Framework 4. Puede descargar IronPython 2.6.1 para .NET Framework 4 de CodePlex.

Para crear una clase dinámica personalizada

  1. En el menú Archivo de Visual Studio, seleccione Nuevo y haga clic en Proyecto.

  2. En el cuadro de diálogo Nuevo proyecto, en el recuadro Tipos de proyecto, asegúrese de haber seleccionado Windows. En el recuadro Plantillas, seleccione Aplicación de consola. En el cuadro Nombre, escriba DynamicIronPythonSample y haga clic en Aceptar. Se crea el nuevo proyecto.

  3. Si usa Visual Basic, haga clic con el botón secundario en el proyecto DynamicIronPythonSample y, a continuación, haga clic en Propiedades. Haga clic en la ficha Referencias. Haga clic en el botón Agregar. Si usa Visual C#, en el Explorador de soluciones, haga clic con el botón secundario en la carpeta Referencias y, a continuación, haga clic en Agregar referencia.

  4. En la pestaña Examinar, busque la carpeta donde están instaladas las bibliotecas de IronPython. Por ejemplo, C:\Archivos de programa\IronPython 2.6 para .NET Framework 4. Seleccione las bibliotecas IronPython.dll, IronPython.Modules.dll, Microsoft.Scripting.dll y Microsoft.Dynamic.dll. Haga clic en Aceptar.

  5. Si usa Visual Basic, modifique el archivo Module1.vb. Si usa Visual C#, modifique el archivo Program.cs.

  6. En la parte superior del archivo, agregue el código siguiente para importar los espacios de nombres Microsoft.Scripting.Hosting y IronPython.Hosting de las bibliotecas de IronPython.

    Imports Microsoft.Scripting.Hosting
    Imports IronPython.Hosting
    
    using Microsoft.Scripting.Hosting;
    using IronPython.Hosting;
    
  7. En el método Main, agregue el código siguiente para crear un nuevo objeto Microsoft.Scripting.Hosting.ScriptRuntime para hospedar las bibliotecas de IronPython. El objeto ScriptRuntime carga el módulo random.py de la biblioteca de 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. Después de que el código cargue el módulo random.py, agregue el código siguiente para crear una matriz de enteros. La matriz se pasa al método shuffle del módulo random.py, que ordena los valores de la matriz de forma aleatoria.

    ' 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. Guarde el archivo y presione CTRL+F5 para compilar y ejecutar la aplicación.

Vea también

Referencia

System.Dynamic

System.Dynamic.DynamicObject

dynamic (Referencia de C#)

Conceptos

Enlace en tiempo de compilación y en tiempo de ejecución (Visual Basic)

Otros recursos

Nuevos tutoriales (Visual C# y Visual Basic)