Exemplarische Vorgehensweise: Erstellen und Verwenden von dynamischen Objekten (C# und Visual Basic)

Dynamische Objekte machen Member wie etwa Eigenschaften und Methoden zur Laufzeit anstatt zur Kompilierzeit verfügbar. Dadurch können Sie Objekte erstellen, mit denen Sie mit Strukturen arbeiten können, die keinem statischen Typ oder Format entsprechen. Sie können z.B. ein dynamisches Objekt verwenden, um auf das HTML-DOM (Document Object Model) zu verweisen, das eine Kombination aus gültigen HTML-Markupelementen und Attributen enthalten kann. Da jedes HTML-Dokument eindeutig ist, werden die Elemente für ein bestimmtes HTML-Dokument zur Laufzeit bestimmt. Eine gängige Methode zum Verweisen eines Attributes auf ein HTML-Element ist, den Namen des Attributs an die GetProperty-Methode des Elements weiterzugeben. Rufen Sie zum Verweisen des id-Attributs auf das HTML-Element <div id="Div1"> zuerst einen Verweis auf das <div>-Element ab, und verwenden Sie anschließend divElement.GetProperty("id"). Wenn Sie ein dynamisches Objekt verwenden, können Sie auf das id-Attribut als divElement.id verweisen.

Dynamische Objekte bieten außerdem einfachen Zugriff auf dynamische Sprachen wie IronPython und IronRuby. Sie können ein dynamisches Objekt verwenden, um sich auf ein dynamisches Skript zu beziehen, das zur Laufzeit ausgeführt wird.

Sie verweisen auf ein dynamisches Objekt mithilfe der späten Bindung. Geben Sie in C# den Typ eines spät gebundenen Objekts als dynamic an. Geben Sie in Visual Basic den Typ eines spät gebundenen Objekts als Object an. Weitere Informationen finden Sie unter dynamic und Frühes und spätes Binden.

Sie können benutzerdefinierte dynamische Objekte erstellen, indem Sie die Klassen im System.Dynamic-Namespace verwenden. Sie können z.B. ein ExpandoObject erstellen und die Member dieses Objekts zur Laufzeit angeben. Sie können auch einen eigenen Typ erstellen, der die DynamicObject-Klasse erbt. Sie können die Member dieser DynamicObject-Klasse anschließend außer Kraft setzen, um dynamische Funktionen zur Laufzeit bereitzustellen.

Dieser Artikel enthält zwei voneinander unabhängige exemplarische Vorgehensweisen:

  • Erstellen Sie ein benutzerdefiniertes Objekt, das die Inhalte einer Textdatei als Eigenschaften eines Objekts weitergibt.

  • Erstellen Sie ein Objekt, das die IronPython-Bibliothek verwendet.

Sie können wählen, ob Sie eine Vorgehensweise oder beide absolvieren möchten (dabei spielt die Reihenfolge keine Rolle).

Voraussetzungen

Hinweis

Auf Ihrem Computer werden möglicherweise andere Namen oder Speicherorte für die Benutzeroberflächenelemente von Visual Studio angezeigt als die in den folgenden Anweisungen aufgeführten. Diese Elemente sind von der jeweiligen Visual Studio-Version und den verwendeten Einstellungen abhängig. Weitere Informationen finden Sie unter Personalisieren der IDE.

  • Wenn Sie die zweite exemplarische Vorgehensweise absolvieren möchten, installieren Sie IronPython für .NET. Wechseln Sie zur Downloadseite, um die neueste Version zu erhalten.

Erstellen eines benutzerdefinierten dynamischen Objekts

In der ersten exemplarischen Vorgehensweise wird ein benutzerdefiniertes dynamisches Objekt definiert, mit dem der Inhalt einer Textdatei durchsucht wird. Mit einer dynamischen Eigenschaft wird der Text angegeben, nach dem gesucht werden soll. Wenn aufrufender Code z.B. dynamicFile.Sample angibt, gibt die dynamische Klasse eine generische Liste von Zeichenfolgen zurück, die alle Zeilen aus der Datei enthält, die mit „Sample“ anfangen. Die Groß- und Kleinschreibung wird bei der Suche nicht berücksichtigt. Die dynamische Klasse unterstützt auch zwei optionale Argumente. Das erste Argument ist ein Enumerationswert einer Suchoption, der angibt, dass die dynamische Klasse am Beginn, am Ende oder an einer beliebigen Stelle der Zeile nach Übereinstimmungen suchen soll. Das zweite Argument gibt an, dass die dynamische Klasse führende und nachfolgende Leerzeichen vor dem Suchvorgang aus jeder Zeile entfernen soll. Wenn aufrufender Code z.B. dynamicFile.Sample(StringSearchOption.Contains) angibt, sucht die dynamische Klasse an einer beliebigen Stelle in der Zeile nach „Sample“. Wenn der aufrufende Code dynamicFile.Sample(StringSearchOption.StartsWith, false) angibt, sucht die dynamische Klasse am Beginn jeder Zeile nach „Sample“ und entfernt keine führenden oder nachfolgenden Leerzeichen. Standardmäßig sucht die dynamische Klasse nach Übereinstimmungen am Beginn jeder Zeile und entfernt führende oder nachfolgende Leerzeichen.

So erstellen Sie eine benutzerdefinierte dynamische Klasse

  1. Starten Sie Visual Studio.

  2. Wählen Sie Neues Projekt erstellen aus.

  3. Wählen Sie im Dialogfeld Neues Projekt erstellen die Option „C#“ oder „Visual Basic“ aus, klicken Sie auf Konsolenanwendung und anschließend auf Weiter.

  4. Geben Sie im Dialogfeld Neues Projekt konfigurieren für Projektname den Text DynamicSample ein, und klicken Sie dann auf Weiter.

  5. Wählen Sie im Dialogfeld Zusätzliche Informationen für Zielframework die Option .NET 5.0 (aktuell) aus, und klicken Sie dann auf Erstellen.

    Das neue Projekt wird erstellt.

  6. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt „DynamicSample“, und klicken Sie anschließend auf Hinzufügen > Klasse. Geben Sie im Feld Name den Text ReadOnlyFile ein, und klicken Sie dann auf Hinzufügen.

    Eine neue Datei wird hinzugefügt, die die Klasse „ReadOnlyFile“ enthält.

  7. Fügen Sie am Anfang der Datei ReadOnlyFile.cs oder ReadOnlyFile.vb den folgenden Code hinzu, um die Namespaces System.IO und System.Dynamic zu importieren.

    using System.IO;
    using System.Dynamic;
    
    Imports System.IO
    Imports System.Dynamic
    
  8. Das benutzerdefinierte dynamische Objekt verwendet einen Enumerationswert, um die Suchkriterien zu bestimmen. Fügen Sie vor der class-Anweisung die folgende Enumerationsdefinition ein.

    public enum StringSearchOption
    {
        StartsWith,
        Contains,
        EndsWith
    }
    
    Public Enum StringSearchOption
        StartsWith
        Contains
        EndsWith
    End Enum
    
  9. Aktualisieren Sie die class-Anweisung wie im folgenden Beispiel, um die DynamicObject-Klasse zu erben.

    class ReadOnlyFile : DynamicObject
    
    Public Class ReadOnlyFile
        Inherits DynamicObject
    
  10. Fügen Sie den folgenden Code zur ReadOnlyFile-Klasse hinzu, um ein privates Feld für den Dateipfad und einen Konstruktor für die ReadOnlyFile-Klasse zu definieren.

    // 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;
    }
    
    ' 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
    
  11. Fügen Sie die folgende GetPropertyValue-Methode zu der ReadOnlyFile-Klasse hinzu. Die GetPropertyValue-Methode akzeptiert Suchkriterien als Eingabe und gibt die Zeilen aus einer Textdatei zurück, die den Suchkriterien entsprechen. Die von der ReadOnlyFile-Klasse bereitgestellten Methoden rufen die GetPropertyValue-Methode auf, um ihre entsprechenden Ergebnisse abzurufen.

    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;
    }
    
    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
    
  12. Fügen Sie nach der GetPropertyValue-Methode den folgenden Code hinzu, um die TryGetMember-Methode der DynamicObject-Klasse zu überschreiben. Die Methode TryGetMember wird aufgerufen, wenn ein Member einer dynamischen Klasse angefordert wird und keine Argumente angegeben werden. Das Argument binder enthält Informationen über den Member, auf den verwiesen wurde. Das Argument result verweist auf das Ergebnis, das für den angegebenen Member zurückgegeben wird. Die Methode TryGetMember gibt einen booleschen Wert zurück, der true zurückgibt, wenn der angeforderte Member vorhanden ist; andernfalls wird false zurückgegeben.

    // 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;
    }
    
    ' 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
    
  13. Fügen Sie nach der TryGetMember-Methode den folgenden Code hinzu, um die TryInvokeMember-Methode der DynamicObject-Klasse zu überschreiben. Die TryInvokeMember-Methode wird aufgerufen, wenn ein Mitglied einer dynamischen Klasse mit Argumenten angefordert wird. Das Argument binder enthält Informationen über den Member, auf den verwiesen wurde. Das Argument result verweist auf das Ergebnis, das für den angegebenen Member zurückgegeben wird. Das Argument args enthält ein Array von Argumenten, die an den Member weitergegeben werden. Die Methode TryInvokeMember gibt einen booleschen Wert zurück, der true zurückgibt, wenn der angeforderte Member vorhanden ist; andernfalls wird false zurückgegeben.

    Die benutzerdefinierte Version der Methode TryInvokeMember erwartet, dass es sich beim ersten Argument um einen Wert der StringSearchOption-Enumeration handelt, die Sie in einem vorherigen Schritt definiert haben. Die Methode TryInvokeMember erwartet, dass es sich beim zweiten Argument um einen booleschen Wert handelt. Wenn es sich bei einem oder beiden Argumenten um gültige Werte handelt, werden sie an die Methode GetPropertyValue zum Abrufen der Werte weitergegeben.

    // 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;
    }
    
    ' 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
    
  14. Speichern und schließen Sie die Datei.

So erstellen Sie eine Beispieltextdatei

  1. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt „DynamicSample“, und klicken Sie anschließend auf Hinzufügen > Neues Element. Wählen Sie im Bereich Installierte Vorlagen Allgemein aus, und wählen Sie anschließend die Vorlage Textdatei aus. Übernehmen Sie im Feld Name den Standardnamen der Datei TextFile1.txt, und klicken Sie dann auf Hinzufügen. Eine neue Textdatei wird zum Projekt hinzugefügt.

  2. Kopieren Sie den folgenden Text in die Datei 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
    
  3. Speichern und schließen Sie die Datei.

So erstellen Sie eine Beispielanwendung, die das benutzerdefinierte dynamische Objekt enthält

  1. Doppelklicken Sie im Projektmappen-Explorer auf die Datei Program.vb (wenn Sie Visual Basic verwenden) oder auf die Datei Program.cs (wenn Sie Visual C# verwenden).

  2. Fügen Sie der Main-Prozedur den folgenden Code hinzu, um eine Instanz der Klasse ReadOnlyFile für die Datei TextFile1.txt zu erstellen. Der Code verwendet spätes Binden, um dynamische Member aufzurufen und die Textzeilen abzurufen, die die Zeichenfolge „Customer“ (Kunde) enthalten.

    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);
    }
    
    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
    
  3. Speichern Sie die Datei, und drücken Sie STRG+F5, um die Anwendung zu erstellen und auszuführen.

Aufrufen einer Bibliothek in einer dynamischen Sprache

In der folgenden exemplarischen Vorgehensweise wird ein Projekt erstellt, das auf eine Bibliothek zugreift, die in der dynamischen Sprache IronPython geschrieben ist.

So erstellen Sie eine benutzerdefinierte dynamische Klasse

  1. Klicken Sie in Visual Studio auf Datei > Neu > Projekt.

  2. Wählen Sie im Dialogfeld Neues Projekt erstellen die Option „C#“ oder „Visual Basic“ aus, klicken Sie auf Konsolenanwendung und anschließend auf Weiter.

  3. Geben Sie im Dialogfeld Neues Projekt konfigurieren für Projektname den Text DynamicIronPythonSample ein, und klicken Sie dann auf Weiter.

  4. Wählen Sie im Dialogfeld Zusätzliche Informationen für Zielframework die Option .NET 5.0 (aktuell) aus, und klicken Sie dann auf Erstellen.

    Das neue Projekt wird erstellt.

  5. Installieren Sie das NuGet-Paket IronPython.

  6. Wenn Sie Visual Basic verwenden, bearbeiten Sie die Datei Program.vb. Wenn Sie Visual C# verwenden, bearbeiten Sie die Datei Program.cs.

  7. Fügen Sie am Anfang der Datei den folgenden Code hinzu, um die Namespaces Microsoft.Scripting.Hosting und IronPython.Hosting aus den IronPython-Bibliotheken zu importieren sowie den Namespace System.Linq.

    using System.Linq;
    using Microsoft.Scripting.Hosting;
    using IronPython.Hosting;
    
    Imports Microsoft.Scripting.Hosting
    Imports IronPython.Hosting
    Imports System.Linq
    
  8. Fügen Sie in der Main-Methode den folgenden Code zum Erstellen eines neuen Microsoft.Scripting.Hosting.ScriptRuntime-Objekts zum Hosten der IronPython-Bibliotheken hinzu. Das ScriptRuntime-Objekt lädt das IronPython-Bibliotheksmodul „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.");
    
    ' 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")
    Dim py = Python.CreateRuntime()
    Dim random As Object = py.UseFile("random.py")
    Console.WriteLine("random.py loaded.")
    
  9. Fügen Sie nach dem Code zum Laden des Moduls „random.py“ den folgenden Code zum Erstellen eines Arrays von ganzen Zahlen hinzu. Das Array wird an die shuffle-Methode des Moduls „random.py“ übergeben, das die Werte im Array per Zufall sortiert.

    // 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("-------------------");
    }
    
    ' 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
    
  10. Speichern Sie die Datei, und drücken Sie STRG+F5, um die Anwendung zu erstellen und auszuführen.

Siehe auch