动态对象在运行时公开属性和方法等成员,而不是在编译时公开。 动态对象使你能够创建对象来处理与静态类型或格式不匹配的结构。 例如,可以使用动态对象引用 HTML 文档对象模型(DOM),该模型可以包含有效 HTML 标记元素和属性的任意组合。 由于每个 HTML 文档都是唯一的,因此特定 HTML 文档的成员在运行时确定。 引用 HTML 元素属性的常见方法是将属性的名称传递给 GetProperty 元素的方法。 若要引用 id HTML 元素 <div id="Div1">的属性,首先获取对元素的 <div> 引用,然后使用 divElement.GetProperty("id")。 如果使用动态对象,则可以将 id 属性引用为 divElement.id.
动态对象还提供对动态语言(如 IronPython 和 IronRuby)的便捷访问。 可以使用动态对象来引用在运行时解释的动态脚本。
您可以通过使用后期绑定来引用动态对象。 将后期绑定对象的类型指定为 dynamic。有关详细信息,请参阅 动态。
可以使用命名空间中的 System.Dynamic 类创建自定义动态对象。 例如,可以在运行时创建 ExpandoObject 并指定该对象的成员。 还可以创建继承该 DynamicObject 类的自己的类型。 然后,可以覆盖类 DynamicObject 的成员以提供运行时的动态功能。
本文包含两个独立的演练:
- 创建一个自定义对象,该对象将文本文件的内容动态公开为对象的属性。
- 创建使用
IronPython库的项目。
先决条件
- 安装了具有 .NET 桌面开发工作负载的 Visual Studio 2022 版本 17.3 或更高版本。 当您选择此工作负载时,会包含 .NET 7 SDK。
注释
计算机可能会在以下说明中显示某些 Visual Studio 用户界面元素的不同名称或位置。 你拥有的 Visual Studio 版本以及所使用的设置决定了这些元素。 有关更多信息,请参阅 自定义 IDE。
- 对于第二个演练,请安装 IronPython for .NET。 转到“ 下载”页 以获取最新版本。
创建自定义动态对象
第一个演练定义一个自定义动态对象,用于搜索文本文件的内容。 动态属性指定要搜索的文本。 例如,如果调用代码指定 dynamicFile.Sample,动态类将返回一个泛型字符串列表,其中包含以“Sample”开头的文件中的所有行。 搜索不区分大小写。 动态类还支持两个可选参数。 第一个参数是一个搜索选项枚举值,该值指定动态类应在行开头、行尾或行中的任意位置搜索匹配项。 第二个参数指定动态类应在搜索之前剪裁每行的前导和尾随空格。 例如,如果调用代码指定 dynamicFile.Sample(StringSearchOption.Contains),动态类将在行中的任意位置搜索“Sample”。 如果调用代码指定 dynamicFile.Sample(StringSearchOption.StartsWith, false),则动态类在每个行的开头搜索“Sample”,并且不会删除前导和尾随空格。 动态类的默认行为是在每行开头搜索匹配项,并删除前导和尾随空格。
创建自定义动态类
启动 Visual Studio。 选择 “创建新项目”。 在“ 创建新项目 ”对话框中,选择“C#”,选择“ 控制台应用程序”,然后选择“ 下一步”。 在“配置新项目”对话框中,输入DynamicSample项目名称,然后选择“下一步”。 在“其他信息”对话框中,为目标框架选择 .NET 7.0(当前),然后选择“创建”。 在 解决方案资源管理器中,右键单击 DynamicSample 项目并选择 “添加>类”。 在 “名称 ”框中,键入 ReadOnlyFile,然后选择“ 添加”。 在 ReadOnlyFile.cs 或 ReadOnlyFile.vb 文件的顶部,添加以下代码以导入 System.IO 和 System.Dynamic 命名空间。
using System.IO;
using System.Dynamic;
自定义动态对象使用枚举来确定搜索条件。 在类语句之前,添加以下枚举定义。
public enum StringSearchOption
{
StartsWith,
Contains,
EndsWith
}
更新类语句以继承 DynamicObject 类,如以下代码示例所示。
class ReadOnlyFile : DynamicObject
将以下代码添加到 ReadOnlyFile 类,以定义文件路径的专用字段和类的 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;
}
- 将以下
GetPropertyValue方法添加到ReadOnlyFile类。 该方法GetPropertyValue以搜索条件作为输入,并返回与该搜索条件匹配的文本文件中的行。ReadOnlyFile类提供的动态方法调用GetPropertyValue方法来检索各自的结果。
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;
}
在 GetPropertyValue 方法后,添加以下代码以替代 TryGetMember 类的 DynamicObject 方法。 一旦请求动态类的成员且未指定任何参数,就会调用TryGetMember方法。 binder 参数包含有关所引用成员的信息,result 参数引用的是为指定成员返回的结果。 该TryGetMember方法返回一个布尔值,如果请求的成员存在,则返回true;否则返回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;
}
在 TryGetMember 方法后,添加以下代码以替代 TryInvokeMember 类的 DynamicObject 方法。 TryInvokeMember当使用参数请求动态类的成员时调用该方法。 binder 参数包含有关所引用成员的信息,result 参数引用的是为指定成员返回的结果。 该 args 参数包含传递给成员的参数的数组。 该TryInvokeMember方法返回一个布尔值,如果请求的成员存在,则返回true;否则返回false。
方法的 TryInvokeMember 自定义版本要求第一个参数是上一步中定义的枚举中的值 StringSearchOption 。 该方法 TryInvokeMember 要求第二个参数为布尔值。 如果一个或两个参数都是有效值,则会传递给 GetPropertyValue 方法以检索结果。
// 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;
}
保存并关闭该文件。
创建示例文本文件
在 解决方案资源管理器中,右键单击 DynamicSample 项目,然后选择“ 添加新>项”。 在“ 已安装的模板 ”窗格中,选择“ 常规”,然后选择 “文本文件 ”模板。 在“名称”框中保留默认TextFile1.txt 名称,然后选择“添加”。 将以下文本复制到 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
保存并关闭该文件。
创建使用自定义动态对象的示例应用程序
在 解决方案资源管理器中,双击 Program.cs 文件。 将以下代码添加到Main过程,为 ReadOnlyFile 文件创建类的实例。 该代码使用后期绑定调用动态成员并检索包含字符串“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);
}
保存文件,然后按 Ctrl+F5 生成并运行应用程序。
调用动态语言库
以下演练将创建一个项目,该项目可以访问由动态语言 IronPython 编写的库。
创建自定义动态类
在 Visual Studio 中,选择“文件”、“新建”>、“项目”。 在“ 创建新项目 ”对话框中,选择“C#”,选择“ 控制台应用程序”,然后选择“ 下一步”。 在“配置新项目”对话框中,输入DynamicIronPythonSample项目名称,然后选择“下一步”。 在“其他信息”对话框中,为目标框架选择 .NET 7.0(当前),然后选择“创建”。 安装 IronPython NuGet 包。 编辑 Program.cs 文件。 在文件的顶部,添加以下代码以从 IronPython 库和Microsoft.Scripting.Hosting命名空间导入IronPython.Hosting和System.Linq命名空间。
using System.Linq;
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;
在 Main 方法中,添加以下代码以创建新 Microsoft.Scripting.Hosting.ScriptRuntime 对象来托管 IronPython 库。 ScriptRuntime 对象加载了 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.");
加载 random.py 模块的代码后,添加以下代码以创建整数数组。 数组将 shuffle 传递给 random.py 模块的方法,该模块会随机对数组中的值进行排序。
// 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("-------------------");
}
保存文件,然后按 Ctrl+F5 生成并运行应用程序。