Tutorial: Crear y usar objetos dinámicos en C#

Los objetos dinámicos exponen miembros como propiedades y métodos en tiempo de ejecución, en lugar de en tiempo de compilación. Los objetos dinámicos le permiten crear objetos para trabajar con estructuras que no coinciden con un formato o tipo estático. Por ejemplo, puede usar un objeto dinámico para hacer referencia a Document Object Model (DOM) HTML, que puede contener cualquier combinación de atributos y elementos de marcado HTML válidos. Dado que cada documento HTML es único, los miembros de un documento HTML específico se determinan en tiempo de ejecución. Un método común para hacer referencia a un atributo de un elemento HTML 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 debe obtener una referencia al elemento <div> y, después, usar divElement.GetProperty("id"). Si usa un objeto dinámico, puede hacer referencia al atributo id como divElement.id.

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

Para hacer referencia a un objeto dinámico, use un enlace en tiempo de ejecución. Especifique el tipo de un objeto enlazado en tiempo de ejecución como dynamic. Para obtener más información, consulte dynamic.

Puede crear objetos dinámicos personalizados con las clases del espacio de nombres System.Dynamic. Por ejemplo, puede crear un objeto ExpandoObject y especificar los miembros de ese objeto en tiempo de ejecución. También puede crear su propio tipo que hereda la clase DynamicObject. Después, puede invalidar los miembros de la clase DynamicObject para proporcionar funciones dinámicas en tiempo de ejecución.

Este artículo contiene dos tutoriales independientes:

  • Crear un objeto personalizado que expone dinámicamente el contenido de un archivo de texto como propiedades de un objeto.
  • Crear un proyecto que usa una biblioteca IronPython.

Requisitos previos

Nota:

Es posible que tu equipo muestre nombres o ubicaciones diferentes para algunos de los elementos de la interfaz de usuario de Visual Studio en las siguientes instrucciones. 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 Personalizar el IDE.

Creación de un objeto dinámico personalizado

En el primer tutorial se define un objeto dinámico personalizado que busca en el contenido de un archivo de texto. Una propiedad dinámica especifica el texto que se va a 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 de enumeración de opción de búsqueda que especifica que la clase dinámica debe buscar coincidencias al principio de la línea, al final de la línea o en cualquier parte de la línea. El segundo argumento especifica que la clase dinámica debe 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" al principio de cada línea y no quita los espacios iniciales y finales. El comportamiento predeterminado de la clase dinámica es buscar una coincidencia al principio de cada línea y quitar los espacios iniciales y finales.

Crear una clase dinámica personalizada

Inicie Visual Studio. Seleccione Crear un proyecto. En el cuadro de diálogo Crear un proyecto, seleccione C#, después Aplicación de consola y luego Siguiente. En el cuadro de diálogo Configurar el nuevo proyecto, escriba DynamicSample como Nombre del proyecto y, después, seleccione Siguiente. En el cuadro de diálogo Información adicional, seleccione .NET 7.0 (actual) para Plataforma de destino y después Crear. En el Explorador de soluciones, haga clic con el botón derecho en el proyecto DynamicSample y seleccione Agregar>Clase. En el cuadro Nombre, escriba ReadOnlyFile y, después, seleccione Agregar. En la parte superior del archivo ReadOnlyFile.cs o ReadOnlyFile.vb, agregue el código siguiente para importar los espacios de nombres System.IO y System.Dynamic.

using System.IO;
using System.Dynamic;

El objeto dinámico personalizado usa una enumeración para determinar los criterios de búsqueda. Antes de la instrucción de clase, agregue la siguiente definición de enumeración.

public enum StringSearchOption
{
    StartsWith,
    Contains,
    EndsWith
}

Actualice la instrucción de clase para heredar la clase DynamicObject, como se muestra en el ejemplo de código siguiente.

class ReadOnlyFile : DynamicObject

Agregue el código siguiente a la clase ReadOnlyFile para definir un campo privado para la ruta de acceso y un constructor para la clase ReadOnlyFile.

// 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;
}
  1. Agregue el método GetPropertyValue siguiente a la clase ReadOnlyFile. El método GetPropertyValue toma como entrada criterios de búsqueda y devuelve las líneas de un archivo de texto que coinciden con los criterios de búsqueda. Los métodos dinámicos proporcionados por la clase ReadOnlyFile llaman al método GetPropertyValue para recuperar los resultados correspondientes.
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;
}

Después del método GetPropertyValue, agregue el código siguiente para invalidar 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 especifican argumentos. El argumento binder contiene información sobre el miembro al que se hace 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. En caso contrario, devuelve false.

// 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;
}

Después del método TryGetMember, agregue el código siguiente para invalidar el método TryInvokeMember de la clase DynamicObject. Se llama al método TryInvokeMember cuando se solicita un miembro de una clase dinámica con argumentos. El argumento binder contiene información sobre el miembro al que se hace referencia y el argumento result hace referencia al resultado devuelto para el miembro especificado. El argumento args contiene una matriz de los argumentos que se pasan al miembro. El método TryInvokeMember devuelve un valor booleano que devuelve true si el miembro solicitado existe. En caso contrario, devuelve false.

La versión personalizada del método TryInvokeMember espera que el primer argumento sea un valor del enumerador StringSearchOption que se ha definido 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 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;
}

Guarde y cierre el archivo.

Creación de un archivo de texto de ejemplo

En el Explorador de soluciones, haga clic con el botón derecho en el proyecto DynamicSample y seleccione Agregar>Nuevo elemento. En el panel Plantillas instaladas seleccione General y, luego, la plantilla Archivo de texto. Deje el nombre predeterminado TextFile1.txt en el cuadro Nombre y después seleccione Agregar. Copie el texto siguiente en el archivo TextFile1.txt.

List of customers and suppliers

Supplier: Lucerne Publishing (https://www.lucernepublishing.com/)
Customer: Preston, Chris
Customer: Hines, Patrick
Customer: Cameron, Maria
Supplier: Graphic Design Institute (https://www.graphicdesigninstitute.com/)
Supplier: Fabrikam, Inc. (https://www.fabrikam.com/)
Customer: Seubert, Roxanne
Supplier: Proseware, Inc. (http://www.proseware.com/)
Customer: Adolphi, Stephan
Customer: Koch, Paul

Guarde y cierre el archivo.

Creación de una aplicación de ejemplo que usa el objeto dinámico personalizado

En el Explorador de soluciones, haga doble clic en el archivo Program.cs. Agregue el código siguiente al procedimiento Main para crear una instancia de la clase ReadOnlyFile para el archivo TextFile1.txt. El código usa el enlace en tiempo de ejecución para llamar a miembros dinámicos y recuperar líneas de texto que contienen la cadena "Customer".

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);
}

Guarde el archivo y presione Ctrl+F5 para compilar y ejecutar la aplicación.

Llamada a una biblioteca de lenguaje dinámico

En el tutorial siguiente se crea un proyecto que accede a una biblioteca escrita en el lenguaje dinámico IronPython.

Para crear una clase dinámica personalizada

Abra Visual Studio, seleccione Archivo>Nuevo>Proyecto. En el cuadro de diálogo Crear un proyecto, seleccione C#, después Aplicación de consola y luego Siguiente. En el cuadro de diálogo Configurar el nuevo proyecto, escriba DynamicIronPythonSample como Nombre del proyecto y, después, seleccione Siguiente. En el cuadro de diálogo Información adicional, seleccione .NET 7.0 (actual) para Plataforma de destino y después Crear. Instale el paquete NuGet IronPython. Edite el archivo Program.cs. 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, y el espacio de nombres System.Linq.

using System.Linq;
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;

En el método Main, agregue el código siguiente para crear un objeto Microsoft.Scripting.Hosting.ScriptRuntime que hospede las bibliotecas de IronPython. El objeto ScriptRuntime carga el módulo de biblioteca de IronPython random.py.

// Set the current directory to the IronPython libraries.
System.IO.Directory.SetCurrentDirectory(
   Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) +
   @"\IronPython 2.7\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.");

Una vez que el código haya cargado 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 aleatoriamente los valores de la matriz.

// 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("-------------------");
}

Guarde el archivo y presione Ctrl+F5 para compilar y ejecutar la aplicación.

Consulte también