Walkthrough: Creating and Using Dynamic Objects in Visual Basic
Dynamic objects expose members such as properties and methods at run time, instead of at compile time. This enables you to create objects to work with structures that do not match a static type or format. For example, you can use a dynamic object to reference the HTML Document Object Model (DOM), which can contain any combination of valid HTML markup elements and attributes. Because each HTML document is unique, the members for a particular HTML document are determined at run time. A common method to reference an attribute of an HTML element is to pass the name of the attribute to the GetProperty
method of the element. To reference the id
attribute of the HTML element <div id="Div1">
, you first obtain a reference to the <div>
element, and then use divElement.GetProperty("id")
. If you use a dynamic object, you can reference the id
attribute as divElement.id
.
Dynamic objects also provide convenient access to dynamic languages such as IronPython and IronRuby. You can use a dynamic object to refer to a dynamic script that is interpreted at run time.
You reference a dynamic object by using late binding. You specify the type of a late-bound object as Object
. For more information, see [Early and Late Binding.
You can create custom dynamic objects by using the classes in the System.Dynamic namespace. For example, you can create an ExpandoObject and specify the members of that object at run time. You can also create your own type that inherits the DynamicObject class. You can then override the members of the DynamicObject class to provide run-time dynamic functionality.
This article contains two independent walkthroughs:
Create a custom object that dynamically exposes the contents of a text file as properties of an object.
Create a project that uses an
IronPython
library.
You can do either one of these or both of them, and if you do both, the order doesn't matter.
Prerequisites
- Visual Studio 2019 version 16.9 or a later version with the .NET desktop development workload installed. The .NET 5 SDK is automatically installed when you select this workload.
Note
Your computer might show different names or locations for some of the Visual Studio user interface elements in the following instructions. The Visual Studio edition that you have and the settings that you use determine these elements. For more information, see Personalizing the IDE.
- For the second walkthrough, install IronPython for .NET. Go to their Download page to obtain the latest version.
Create a Custom Dynamic Object
The first walkthrough defines a custom dynamic object that searches the contents of a text file. A dynamic property specifies the text to search for. For example, if calling code specifies dynamicFile.Sample
, the dynamic class returns a generic list of strings that contains all of the lines from the file that begin with "Sample". The search is case-insensitive. The dynamic class also supports two optional arguments. The first argument is a search option enum value that specifies that the dynamic class should search for matches at the start of the line, the end of the line, or anywhere in the line. The second argument specifies that the dynamic class should trim leading and trailing spaces from each line before searching. For example, if calling code specifies dynamicFile.Sample(StringSearchOption.Contains)
, the dynamic class searches for "Sample" anywhere in a line. If calling code specifies dynamicFile.Sample(StringSearchOption.StartsWith, false)
, the dynamic class searches for "Sample" at the start of each line, and does not remove leading and trailing spaces. The default behavior of the dynamic class is to search for a match at the start of each line and to remove leading and trailing spaces.
To create a custom dynamic class
Start Visual Studio.
Select Create a new project.
In the Create a new project dialog, select Visual Basic, select Console Application, and then select Next.
In the Configure your new project dialog, enter
DynamicSample
for the Project name, and then select Next.In the Additional information dialog, select .NET 5.0 (Current) for the Target Framework, and then select Create.
The new project is created.
In Solution Explorer, right-click the DynamicSample project and select Add > Class. In the Name box, type
ReadOnlyFile
, and then select Add.A new file is added that contains the ReadOnlyFile class.
At the top of the ReadOnlyFile.cs or ReadOnlyFile.vb file, add the following code to import the System.IO and System.Dynamic namespaces.
Imports System.IO Imports System.Dynamic
The custom dynamic object uses an enum to determine the search criteria. Before the class statement, add the following enum definition.
Public Enum StringSearchOption StartsWith Contains EndsWith End Enum
Update the class statement to inherit the
DynamicObject
class, as shown in the following code example.Public Class ReadOnlyFile Inherits DynamicObject
Add the following code to the
ReadOnlyFile
class to define a private field for the file path and a constructor for theReadOnlyFile
class.' 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
Add the following
GetPropertyValue
method to theReadOnlyFile
class. TheGetPropertyValue
method takes, as input, search criteria and returns the lines from a text file that match that search criteria. The dynamic methods provided by theReadOnlyFile
class call theGetPropertyValue
method to retrieve their respective 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
After the
GetPropertyValue
method, add the following code to override the TryGetMember method of the DynamicObject class. The TryGetMember method is called when a member of a dynamic class is requested and no arguments are specified. Thebinder
argument contains information about the referenced member, and theresult
argument references the result returned for the specified member. The TryGetMember method returns a Boolean value that returnstrue
if the requested member exists; otherwise it returnsfalse
.' 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
After the
TryGetMember
method, add the following code to override the TryInvokeMember method of the DynamicObject class. The TryInvokeMember method is called when a member of a dynamic class is requested with arguments. Thebinder
argument contains information about the referenced member, and theresult
argument references the result returned for the specified member. Theargs
argument contains an array of the arguments that are passed to the member. The TryInvokeMember method returns a Boolean value that returnstrue
if the requested member exists; otherwise it returnsfalse
.The custom version of the
TryInvokeMember
method expects the first argument to be a value from theStringSearchOption
enum that you defined in a previous step. TheTryInvokeMember
method expects the second argument to be a Boolean value. If one or both arguments are valid values, they are passed to theGetPropertyValue
method to retrieve the results.' 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
Save and close the file.
To create a sample text file
In Solution Explorer, right-click the DynamicSample project and select Add > New Item. In the Installed Templates pane, select General, and then select the Text File template. Leave the default name of TextFile1.txt in the Name box, and then click Add. A new text file is added to the project.
Copy the following text to the TextFile1.txt file.
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
Save and close the file.
To create a sample application that uses the custom dynamic object
In Solution Explorer, double-click the Program.vb file.
Add the following code to the
Main
procedure to create an instance of theReadOnlyFile
class for the TextFile1.txt file. The code uses late binding to call dynamic members and retrieve lines of text that contain the string "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
Save the file and press Ctrl+F5 to build and run the application.
Call a dynamic language library
The following walkthrough creates a project that accesses a library that is written in the dynamic language IronPython.
To create a custom dynamic class
In Visual Studio, select File > New > Project.
In the Create a new project dialog, select Visual Basic, select Console Application, and then select Next.
In the Configure your new project dialog, enter
DynamicIronPythonSample
for the Project name, and then select Next.In the Additional information dialog, select .NET 5.0 (Current) for the Target Framework, and then select Create.
The new project is created.
Install the IronPython NuGet package.
Edit the Program.vb file.
At the top of the file, add the following code to import the
Microsoft.Scripting.Hosting
andIronPython.Hosting
namespaces from the IronPython libraries and theSystem.Linq
namespace.Imports Microsoft.Scripting.Hosting Imports IronPython.Hosting Imports System.Linq
In the Main method, add the following code to create a new
Microsoft.Scripting.Hosting.ScriptRuntime
object to host the IronPython libraries. TheScriptRuntime
object loads the IronPython library module 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") Dim py = Python.CreateRuntime() Dim random As Object = py.UseFile("random.py") Console.WriteLine("random.py loaded.")
After the code to load the random.py module, add the following code to create an array of integers. The array is passed to the
shuffle
method of the random.py module, which randomly sorts the values in the array.' 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
Save the file and press Ctrl+F5 to build and run the application.