动态对象在运行时公开属性和方法等成员,而不是在编译时公开。 动态对象使你能够创建对象来处理与静态类型或格式不匹配的结构。 例如,可以使用动态对象引用 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 生成并运行应用程序。