逐步解說:在 C# 中建立和使用動態物件
動態物件會在執行階段公開成員 (例如屬性和方法),而不是在編譯時期。 動態物件可讓您使用與靜態類型或格式不相符的結構來建立物件。 例如,您可以使用動態物件來參考 HTML 文件物件模型 (DOM),其可包含任何有效的 HTML 標記項目和屬性組合。 由於每個 HTML 文件都是唯一的,因此系統會在執行階段決定特定的 HTML 文件成員。 參考 HTML 項目屬性的常用方法,是將屬性名稱傳遞給項目的 GetProperty
方法。 若要參考 HTML 項目 <div id="Div1">
的 id
屬性,您要先取得 <div>
項目的參考,然後再使用 divElement.GetProperty("id")
。 如果您使用動態物件,則可以 divElement.id
的形式來參考 id
屬性。
動態物件也可讓您方便存取動態語言 (諸如 IronPython 和 IronRuby)。 您可以使用動態物件,參考在執行階段解譯的動態指令碼。
您可以使用晚期繫結,參考動態物件。 您可以將晚期繫結物件的類型指定為 dynamic
。如需詳細資訊,請參閱動態。
您可以在 System.Dynamic 命名空間中使用類別,以建立自訂動態物件。 例如,您可以建立 ExpandoObject,並在執行階段中指定該物件的成員。 您也可以建立繼承 DynamicObject 類別的專屬類型。 接著,您即可覆寫 DynamicObject 類別的成員,以提供執行階段動態功能。
本文包含兩個獨立的逐步解說:
- 建立自訂物件,以將文字檔內容動態公開為物件的屬性。
- 建立專案,以使用
IronPython
程式庫。
必要條件
- 已安裝 .NET 桌面開發工作負載的 Visual Studio 2022 17.3 版或更新版本。 選取此工作負載時會包含 .NET 7 SDK。
注意
在下列指示的某些 Visual Studio 使用者介面項目中,您的電腦可能會顯示不同的名稱或位置: 您所擁有的 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;
自訂動態物件會使用列舉,來判斷搜尋準則。 在 class 陳述式之前,加入下列列舉定義。
public enum StringSearchOption
{
StartsWith,
Contains,
EndsWith
}
如下列程式碼範例所示,更新繼承 DynamicObject
類別的 class 陳述式。
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
方法後面,新增下面程式碼以覆寫 DynamicObject 類別的 TryGetMember 方法。 如果系統要求動態類別的成員,但未指定任何參數,則會呼叫 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
方法後面,新增下面程式碼以覆寫 DynamicObject 類別的 TryInvokeMember 方法。 如果系統要求具有引數的動態類別成員,則會呼叫 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
程序,以針對 TextFile1.txt 檔案建立 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 程式庫和 System.Linq
命名空間的 Microsoft.Scripting.Hosting
和 IronPython.Hosting
命名空間。
using System.Linq;
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;
在 Main 方法中加入下列程式碼,以建立裝載 IronPython 程式庫的新 Microsoft.Scripting.Hosting.ScriptRuntime
物件。 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 模組之後,請加入下列程式碼以建立整數陣列。 系統會將陣列傳遞給 random.py 模組的 shuffle
方法,其會隨機排序陣列中的值。
// 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 以組建並執行應用程式。