チュートリアル: 動的オブジェクトの作成と使用 (C# および Visual Basic)

動的オブジェクトは、コンパイル時ではなく実行時に、プロパティやメソッドなどのメンバーを公開します。これにより、静的な型または形式が一致しない構造体を操作するためのオブジェクトを作成できます。たとえば、有効な HTML マークアップの要素と属性の組み合わせを含めることが可能な HTML ドキュメント オブジェクト モデル (DOM: Document Object Model) を参照する動的オブジェクトを作成できます。各 HTML ドキュメントは一意であるため、特定の HTML ドキュメントのメンバーは実行時に決定されます。HTML 要素の属性を参照するための一般的な方法として、要素の GetProperty メソッドに属性の名前を渡す方法があります。<div id="Div1"> という HTML 要素の id 属性を参照するには、まず <div> 要素への参照を取得し、次に divElement.GetProperty("id") を使用します。動的オブジェクトを使用する場合は、id 属性を divElement.id として参照できます。

動的オブジェクトを使用すると、IronPython や IronRuby などの動的言語に簡単にアクセスすることもできます。動的オブジェクトを使用して、実行時に解釈される動的スクリプトを参照できます。

動的オブジェクトを参照するには、遅延バインディングを使用します。C# では、遅延バインディング オブジェクトの型を dynamic として指定します。Visual Basic では、遅延バインディング オブジェクトの型を Object として指定します。詳細については、「dynamic (C# リファレンス)」および「事前バインディングと遅延バインディング (Visual Basic)」を参照してください。

カスタム動的オブジェクトを作成するには、System.Dynamic 名前空間のクラスを使用します。たとえば、ExpandoObject を作成し、実行時に、このオブジェクトのメンバーを指定できます。DynamicObject クラスを継承する独自の型を作成することもできます。これにより、DynamicObject クラスのメンバーをオーバーライドして、実行時の動的な機能を提供できます。

このチュートリアルでは、次のタスクを行います。

  • テキスト ファイルの内容をオブジェクトのプロパティとして動的に公開するカスタム オブジェクトを作成します。

  • IronPython ライブラリを使用するプロジェクトを作成します。

必須コンポーネント

このチュートリアルを実行するには、IronPython 2.6.1 for .NET 4.0 が必要です。IronPython 2.6.1 for .NET 4.0 は、CodePlex からダウンロードできます。

[!メモ]

お使いのマシンで、Visual Studio ユーザー インターフェイスの一部の要素の名前や場所が、次の手順とは異なる場合があります。これらの要素は、使用している Visual Studio のエディションや独自の設定によって決まります。詳細については、「Visual Studio の設定」を参照してください。

カスタム動的オブジェクトの作成

このチュートリアルで作成する最初のプロジェクトでは、テキスト ファイルの内容を検索するカスタムの動的オブジェクトを定義します。検索するテキストは、動的プロパティの名前で指定します。たとえば、呼び出し元のコードで dynamicFile.Sample が指定されている場合、動的クラスは、ファイル内で "Sample" で始まるすべての行を含む文字列のジェネリック リストを返します。検索では、大文字と小文字が区別されます。動的クラスでは、2 つの省略可能な引数もサポートされています。1 つ目の引数は、検索オプションの列挙値で、動的クラスが行の先頭、末尾、または任意の位置での一致を検索する必要があることを指定します。2 つ目の引数は、動的クラスによって検索の前に各行から先頭と末尾の空白が削除される必要があることを指定します。たとえば、呼び出し元のコードで dynamicFile.Sample(StringSearchOption.Contains) が指定されている場合、動的クラスは行の任意の位置にある "Sample" を検索します。呼び出し元のコードで dynamicFile.Sample(StringSearchOption.StartsWith, false) が指定されている場合、動的クラスは各行の先頭で "Sample" を検索します。このとき、行の先頭と末尾にある空白は削除しません。動的クラスの既定の動作では、各行の先頭での一致が検索され、先頭と末尾にある空白が削除されます。

カスタムの動的クラスを作成するには

  1. Visual Studio を起動します。

  2. [ファイル] メニューの [新規作成] をポイントし、[プロジェクト] をクリックします。

  3. [新しいプロジェクト] ダイアログ ボックスの [プロジェクトの種類] ペインで、[Windows] が選択されていることを確認します。[テンプレート] ペインの [コンソール アプリケーション] を選択します。[名前] ボックスに「DynamicSample」と入力し、[OK] をクリックします。新しいプロジェクトが作成されます。

  4. DynamicSample プロジェクトを右クリックし、[追加] をポイントして、[クラス] をクリックします。[名前] ボックスに「ReadOnlyFile」と入力し、[OK] をクリックします。ReadOnlyFile クラスを含む新しいファイルが追加されます。

  5. ReadOnlyFile.cs ファイルまたは ReadOnlyFile.vb ファイルの先頭に次のコードを追加して、System.IO 名前空間と System.Dynamic 名前空間をインポートします。

    Imports System.IO
    Imports System.Dynamic
    
    using System.IO;
    using System.Dynamic;
    
  6. カスタムの動的オブジェクトは、列挙値を使用して検索条件を確認します。class ステートメントの前に、次の列挙型定義を追加します。

    Public Enum StringSearchOption
        StartsWith
        Contains
        EndsWith
    End Enum
    
    public enum StringSearchOption
    {
        StartsWith,
        Contains,
        EndsWith
    }
    
  7. 次のコード例に示すように、DynamicObject クラスを継承するように class ステートメントを更新します。

    Public Class ReadOnlyFile
        Inherits DynamicObject
    
    class ReadOnlyFile : DynamicObject
    
  8. ReadOnlyFile クラスに次のコードを追加して、ファイル パスのプライベート フィールドと ReadOnlyFile クラスのコンストラクターを定義します。

    ' 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
    
    // 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;
    }
    
  9. ReadOnlyFile クラスに次の GetPropertyValue メソッドを追加します。GetPropertyValue メソッドは、入力として検索条件を受け取り、テキスト ファイル内でその検索条件に一致する行を返します。ReadOnlyFile クラスによって提供される動的メソッドは GetPropertyValue メソッドを呼び出して、それぞれの結果を取得します。

    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
    
    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;
    }
    
  10. GetPropertyValue メソッドの後に次のコードを追加して、DynamicObject クラスの TryGetMember メソッドをオーバーライドします。動的クラスのメンバーが引数なしで要求されると、TryGetMember メソッドが呼び出されます。binder 引数は参照先メンバーに関する情報を含み、result 引数は指定したメンバーについて返された結果を参照します。TryGetMember メソッドは、要求されたメンバーが存在する場合には true を返すブール値を返し、それ以外の場合には false を返すブール値を返します。

    ' 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
    
    // 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;
    }
    
  11. TryGetMember メソッドの後に次のコードを追加して、DynamicObject クラスの TryInvokeMember メソッドをオーバーライドします。動的クラスのメンバーが引数付きで要求されると、TryInvokeMember メソッドが呼び出されます。binder 引数は参照先メンバーに関する情報を含み、result 引数は指定したメンバーについて返された結果を参照します。args 引数は、メンバーに渡される引数の配列を含みます。TryInvokeMember メソッドは、要求されたメンバーが存在する場合には true を返すブール値を返し、それ以外の場合には false を返すブール値を返します。

    TryInvokeMember メソッドのカスタム バージョンは、1 つ目の引数が、前の手順で定義した StringSearchOption 列挙体に含まれるいずれかの値であることを求めます。TryInvokeMember メソッドは、2 つ目の引数がブール値であることを求めます。これらの引数のいずれかまたは両方が有効な値であれば、これらの引数は GetPropertyValue メソッドに渡され、結果が取得されます。

    ' 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
    
    // 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;
    }
    
  12. ファイルを保存して閉じます。

サンプル テキスト ファイルを作成するには

  1. DynamicSample プロジェクトを右クリックし、[追加] をポイントして、[新しい項目] をクリックします。[インストールされたテンプレート] ペインで、[全般] を選択し、[テキスト ファイル] テンプレートを選択します。[名前] ボックスで、既定の名前である TextFile1.txt をそのまま使用し、[追加] をクリックします。プロジェクトに新しいテキスト ファイルが追加されます。

  2. 次のテキストを TextFile1.txt ファイルにコピーします。

    List of customers and suppliers
    
    Supplier: Lucerne Publishing (http://www.lucernepublishing.com/)
    Customer: Preston, Chris
    Customer: Hines, Patrick
    Customer: Cameron, Maria
    Supplier: Graphic Design Institute (http://www.graphicdesigninstitute.com/) 
    Supplier: Fabrikam, Inc. (http://www.fabrikam.com/) 
    Customer: Seubert, Roxanne
    Supplier: Proseware, Inc. (https://www.proseware.com/) 
    Customer: Adolphi, Stephan
    Customer: Koch, Paul
    
  3. ファイルを保存して閉じます。

カスタムの動的オブジェクトを使用するサンプル アプリケーションを作成するには

  1. ソリューション エクスプローラーで、Module1.vb ファイル (Visual Basic を使用している場合) または Program.cs ファイル (Visual C# を使用している場合) をダブルクリックします。

  2. 次のコードを Main プロシージャに追加して、TextFile1.txt ファイルの ReadOnlyFile クラスのインスタンスを作成します。このコードは、遅延バインディングを使用して動的メンバーを呼び出し、"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
    
    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);
    }
    
  3. ファイルを保存し、Ctrl キーを押しながら F5 キーを押して、アプリケーションをビルドおよび実行します。

動的言語ライブラリの呼び出し

このチュートリアルで作成する次のプロジェクトでは、動的言語である IronPython で記述されたライブラリにアクセスします。このプロジェクトを作成するには、IronPython 2.6.1 for .NET 4.0 がインストールされている必要があります。IronPython 2.6.1 for .NET 4.0 は、CodePlex からダウンロードできます。

カスタムの動的クラスを作成するには

  1. Visual Studio で、[ファイル] メニューの [新規作成] をポイントし、[プロジェクト] をクリックします。

  2. [新しいプロジェクト] ダイアログ ボックスの [プロジェクトの種類] ペインで、[Windows] が選択されていることを確認します。[テンプレート] ペインの [コンソール アプリケーション] を選択します。[名前] ボックスに「DynamicIronPythonSample」と入力し、[OK] をクリックします。新しいプロジェクトが作成されます。

  3. Visual Basic を使用している場合は、DynamicIronPythonSample プロジェクトを右クリックし、[プロパティ] をクリックします。[参照設定] タブをクリックします。[追加] ボタンをクリックします。Visual C# を使用している場合は、ソリューション エクスプローラー[参照設定] フォルダーを右クリックし、[参照の追加] をクリックします。

  4. [参照] タブで、IronPython ライブラリがインストールされているフォルダーを参照します。たとえば、C:\Program Files\IronPython 2.6 for .NET 4.0 を参照します。IronPython.dllIronPython.Modules.dllMicrosoft.Scripting.dllMicrosoft.Dynamic.dll の各ライブラリを選択します。[OK] をクリックします。

  5. Visual Basic を使用している場合は、Module1.vb ファイルを編集します。Visual C# を使用している場合は、Program.cs ファイルを編集します。

  6. ファイルの先頭に次のコードを追加して、IronPython ライブラリから Microsoft.Scripting.Hosting 名前空間と IronPython.Hosting 名前空間をインポートします。

    Imports Microsoft.Scripting.Hosting
    Imports IronPython.Hosting
    
    using Microsoft.Scripting.Hosting;
    using IronPython.Hosting;
    
  7. Main メソッドに次のコードを追加して、IronPython ライブラリをホストする新しい Microsoft.Scripting.Hosting.ScriptRuntime オブジェクトを作成します。ScriptRuntime オブジェクトは、IronPython ライブラリ モジュール random.py を読み込みます。

    ' Set the current directory to the IronPython libraries.
    My.Computer.FileSystem.CurrentDirectory = 
       My.Computer.FileSystem.SpecialDirectories.ProgramFiles &
       "\IronPython 2.6 for .NET 4.0\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.")
    
    // Set the current directory to the IronPython libraries.
    System.IO.Directory.SetCurrentDirectory(
       Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + 
       @"\IronPython 2.6 for .NET 4.0\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.");
    
  8. random.py モジュールを読み込むコードの後に、次のコードを追加して整数の配列を作成します。この配列は、random.py モジュールの shuffle メソッドに渡されます。このメソッドは、配列内の値をランダムに並べ替えます。

    ' 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
    
    // 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("-------------------");
    }
    
  9. ファイルを保存し、Ctrl キーを押しながら F5 キーを押して、アプリケーションをビルドおよび実行します。

参照

関連項目

System.Dynamic

System.Dynamic.DynamicObject

dynamic (C# リファレンス)

概念

事前バインディングと遅延バインディング (Visual Basic)

その他の技術情報

dynamic 型の使用 (C# プログラミング ガイド)

動的インターフェイス実装します (ブログ) を外部