チュートリアル: 動的オブジェクトの作成と使用 (C# および Visual Basic)
動的オブジェクトは、プロパティやメソッドなどのメンバーを、コンパイル時ではなく実行時に公開します。 これにより、静的な型や書式に一致しない構造体と連携するオブジェクトを作成することができます。 たとえば、動的オブジェクトを使用して HTML ドキュメント オブジェクト モデル (DOM) を参照することもできます。HTML DOM には、有効な HTML マークアップ要素と属性の任意の組み合わせを含めることができます。 各 HTML ドキュメントは一意であるため、特定の HTML ドキュメントのメンバーは実行時に決定されます。 HTML 要素の属性を参照するための一般的な方法は、属性の名前を要素の GetProperty
メソッドに渡す方法です。 HTML 要素 <div id="Div1">
の id
属性を参照するには、まず <div>
要素への参照を取得し、その後 divElement.GetProperty("id")
を使用します。 動的オブジェクトを使用する場合は、id
属性を divElement.id
として参照できます。
動的オブジェクトを使用すると、IronPython や IronRuby などの動的言語にも簡単にアクセスできます。 動的オブジェクトを使用して、実行時に解釈される動的スクリプトを参照することもできます。
動的オブジェクトを参照するには、遅延バインディングを使用します。 C# では、遅延バインディング オブジェクトの型は dynamic
として指定します。 Visual Basic では、遅延バインディング オブジェクトの型は Object
として指定します。 詳しくは、「dynamic」および「事前バインディングと遅延バインディング」をご覧ください。
カスタムの動的オブジェクトは、System.Dynamic 名前空間内のクラスを使用して作成できます。 たとえば、ExpandoObject を作成し、実行時にそのオブジェクトのメンバーを指定することもできます。 また、DynamicObject クラスを継承する、独自の型を作成することもできます。 その後、DynamicObject クラスのメンバーをオーバーライドして、実行時の動的機能を提供することができます。
この記事には、次の 2 つの独立したチュートリアルがあります。
テキスト ファイルの内容をオブジェクトのプロパティとして動的に公開する、カスタム オブジェクトを作成する。
IronPython
ライブラリを使用するプロジェクトを作成する。
これらのいずれかまたは両方を実行することができます。両方を実行する場合、順序は関係ありません。
前提条件
- .NET デスクトップ開発ワークロードがインストールされている Visual Studio 2019 バージョン 16.9 以降。 このワークロードを選択すると、.NET 5 SDK が自動的にインストールされます。
注意
次の手順で参照している Visual Studio ユーザー インターフェイス要素の一部は、お使いのコンピューターでは名前や場所が異なる場合があります。 これらの要素は、使用している Visual Studio のエディションや独自の設定によって決まります。 詳細については、「IDE をカスタマイズする」をご覧ください。
- 2 番目のチュートリアルでは、IronPython for .NET をインストールします。 最新バージョンを取得するには、ダウンロード ページに移動します。
カスタム動的オブジェクトの作成
最初のチュートリアルでは、テキスト ファイルの内容を検索するカスタム動的オブジェクトを定義します。 動的プロパティにより、検索するテキストが指定されます。 たとえば、呼び出しコードで dynamicFile.Sample
が指定された場合、動的クラスは "Sample" で始まるすべての行をファイルから取得し、それらを含んだ文字列のジェネリック リストを返します。 検索では、大文字と小文字を区別しません。 動的クラスでは、2 つの省略可能な引数もサポートされています。 最初の引数は、検索オプションの列挙値です。この引数では、動的クラスが行の先頭、行の末尾、または行内の任意の場所から一致を検索することを指定します。 2 番目の引数は、動的クラスが検索の前に各行から先頭と末尾の空白をトリミングすることを指定します。 たとえば、呼び出しコードで dynamicFile.Sample(StringSearchOption.Contains)
と指定された場合、動的クラスは行内の任意の場所にある "Sample" を検索します。 呼び出しコードで dynamicFile.Sample(StringSearchOption.StartsWith, false)
と指定された場合、動的クラスは、各行の先頭にある "Sample" を検索し、先頭と末尾のスペースは削除しません。 既定では、動的クラスは各行の先頭で一致を検索し、先頭と末尾のスペースを削除します。
カスタムの動的クラスを作成するには
Visual Studio を起動します。
[新しいプロジェクトの作成] を選択します。
[新しいプロジェクトの作成] ダイアログで、[C#] または [Visual Basic] を選択し、 [コンソール アプリケーション] を選択して、 [次へ] を選択します。
[新しいプロジェクトの構成] ダイアログで、 [プロジェクト名] に「
DynamicSample
」と入力し、 [次へ] を選択します。[追加情報] ダイアログで、 [ターゲット フレームワーク] に [.NET 5.0 (Current)] を選んでから、 [作成] を選択します。
新しいプロジェクトが作成されます。
ソリューション エクスプローラーで、DynamicSample プロジェクトを右リックし、 [追加]>[クラス] を選択します。 [名前] ボックスに「
ReadOnlyFile
」と入力し、 [追加] を選択します。ReadOnlyFile クラスを含んだ新しいファイルが追加されます。
ReadOnlyFile.cs または ReadOnlyFile.vb のファイルの先頭に、次のコードを追加して System.IO および System.Dynamic の名前空間をインポートします。
using System.IO; using System.Dynamic;
Imports System.IO Imports System.Dynamic
カスタム動的オブジェクトでは、列挙型を使用して検索条件を決定します。 クラス ステートメントの前に、次の列挙定義を追加します。
public enum StringSearchOption { StartsWith, Contains, EndsWith }
Public Enum StringSearchOption StartsWith Contains EndsWith End Enum
次のコード例に示すように、クラス ステートメントを更新して
DynamicObject
クラスを継承します。class ReadOnlyFile : DynamicObject
Public Class ReadOnlyFile Inherits 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; }
' 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
次の
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; }
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
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; }
' 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
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 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; }
' 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
ファイルを保存して閉じます。
サンプルのテキスト ファイルを作成するには
ソリューション エクスプローラーで、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
ファイルを保存して閉じます。
カスタム動的オブジェクトを使用するサンプル アプリケーションを作成するには
ソリューション エクスプローラーで、Visual Basic をお使いの場合は Program.vb ファイルを、Visual C# をお使いの場合は 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); }
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
ファイルを保存し、Ctrl+F5 キーを押してアプリケーションをビルドし、実行します。
動的言語ライブラリの呼び出し
次のチュートリアルでは、動的言語 IronPython で記述されたライブラリにアクセスするプロジェクトを作成します。
カスタムの動的クラスを作成するには
Visual Studio で、 [ファイル]>[新規]>[プロジェクト] の順に選択します。
[新しいプロジェクトの作成] ダイアログで、[C#] または [Visual Basic] を選択し、 [コンソール アプリケーション] を選択して、 [次へ] を選択します。
[新しいプロジェクトの構成] ダイアログで、 [プロジェクト名] に「
DynamicIronPythonSample
」と入力し、 [次へ] を選択します。[追加情報] ダイアログで、 [ターゲット フレームワーク] に [.NET 5.0 (Current)] を選んでから、 [作成] を選択します。
新しいプロジェクトが作成されます。
IronPython NuGet パッケージをインストールします。
Visual Basic をお使いの場合は、Program.vb ファイルを編集します。 Visual C# をお使いの場合は、Program.cs ファイルを編集します。
ファイルの先頭に次のコードを追加して、IronPython ライブラリと
System.Linq
名前空間からMicrosoft.Scripting.Hosting
およびIronPython.Hosting
名前空間をインポートします。using System.Linq; using Microsoft.Scripting.Hosting; using IronPython.Hosting;
Imports Microsoft.Scripting.Hosting Imports IronPython.Hosting Imports System.Linq
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.");
' 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") Dim py = Python.CreateRuntime() Dim random As Object = 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("-------------------"); }
' 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
ファイルを保存し、Ctrl+F5 キーを押してアプリケーションをビルドし、実行します。