逐步解說:在 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 程式庫。

必要條件

注意

在下列指示的某些 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.csReadOnlyFile.vb 檔案頂端,新增下列程式碼以匯入 System.IOSystem.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;
}
  1. 將下列 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.HostingIronPython.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 以組建並執行應用程式。

另請參閱