ここまではメソッドの呼び出しをdynamicで行う方法を扱ってきましたが、今回はプロパティの操作をdynamicで行う方法を見ていきます。
プロパティに対するReflectionでの操作は、PropertyInfoをGetPropertyで取得し、GetValue・SetValueで値を取得・設定するというやり方でした。これをdynamicで書くとどうなるかを、MailServiceクラスが持つAddressプロパティを題材に確認します。
対象のプロパティ
題材となるMailServiceクラスには、以下のようにAddressという自動実装プロパティが定義されています。
図1 MailServiceクラスのAddressプロパティ
dynamicでプロパティに値を設定する
Activator.CreateInstanceの戻り値をdynamic型のinstanceで受けている場合、プロパティへの値のセットは通常のインスタンスに対する操作とまったく同じ書き方でできます。
dynamic instance = Activator.CreateInstance(t);
// プロパティの操作
instance?.Address = "AAA";
図2 instance.Addressに”AAA”をセットする
PropertyInfoのSetValueを使う書き方に比べ、instance.Addressに直接代入するだけで済むため、通常のプロパティ操作とほとんど区別のない感覚で書けます。
dynamicでプロパティの値を取得する
値を取得する場合も同様に、instance.Addressとそのまま書くだけで取得できます。
string? address = instance?.Address;
図3 instance.Addressから値を取得する
先ほどセットした”AAA”がそのまま取得できていることが確認できます。
存在しないプロパティ名を指定した場合
dynamicによるメンバーアクセスはコンパイル時にチェックされないため、存在しないプロパティ名を指定してもビルドエラーにはなりません。実行時に初めてエラーとなり、例外が発生します。
string? address = instance?.AddressA;
// 実行時に RuntimeBinderException が発生する
図4 存在しないプロパティ名を指定した場合の例外
「’Anderson.Domain.MailService’ does not contain a definition for ‘AddressA’」という内容のRuntimeBinderExceptionが発生します。dynamicを使う際は、この種の間違いがコンパイル時に検出できない点に注意が必要です。
varで受けた場合の型に注意
instance.Addressの型そのものはdynamic型です。stringで明示的に受け取ればstring型として扱えますが、varで受け取るとdynamicのstring、つまり見た目はstringでも実体はdynamicという型になります。
string? address = instance?.Address; // string型
var addressB = instance?.Address; // dynamic {string} 型
図5 ウォッチウィンドウで型を確認(addressBはdynamic {string})
ウォッチウィンドウで確認すると、stringで受けたaddressは種類が「string」なのに対し、varで受けたaddressBは「dynamic {string}」となっており、見た目の値は同じ”AAA”でも型としては別物であることが分かります。
dynamicのままだとエラーになるメソッドがある
varで受けたdynamicのstringは、渡すメソッドによっては例外になることがあります。代表例がDebug.WriteLineです。
Debug.WriteLine(addressB);
// RuntimeBinderException:
// Cannot dynamically invoke method 'WriteLine' because
// it has a Conditional attribute
図6 Debug.WriteLineにdynamicの値を渡すと発生する例外
これはDebug.WriteLineにConditional属性が付与されていることが関係しており、dynamicの引数では処理できずに例外になります。一方、stringとして明示的に受け取ったaddressを渡せば問題なく動作します。また、同じ引数でもMessageBox.Showのように問題なく動くメソッドもあり、メソッドによって挙動が異なる点に注意が必要です。
Debug.WriteLine(address); // OK(stringとして受けているため)
MessageBox.Show(addressB); // こちらはdynamicのままでもOK
図7 string変換した値とdynamicのままの値、それぞれの使用例
まとめ
プロパティに対する操作も、メソッドの呼び出しと同様にdynamicを使うことで、通常のインスタンス操作とほぼ同じ感覚で書くことができます。ただし、次の2点には注意してください。
1点目は、存在しないメンバー名を指定してもコンパイル時にはエラーにならず、実行時に初めて例外として検出される点です。
2点目は、varで受け取った値の型はdynamicのままになるため、Debug.WriteLineのように呼び出せないメソッドが存在する点です。値をstringなど明確な型に変換してから渡すことで、この問題を回避できます。
目次
はじめに
A01_はじめに
A02_コースの概要
Type
B01_Typeクラスとは
B02_型からTypeを取得する
B03_自前の型からTypeを取得する
B04_インスタンスからTypeを取得する
B05_Typeのインスタンスは1つだけ
Assembly
C01_Assemblyとは
C02_Assemblyの生成_実行中のアセンブリを取得
C03_Assemblyの生成_アセンブリ名で取得
C04_Assemblyの生成_パスを指定して取得
C05_Assemblyの生成_呼び出し元を取得
C06_Assemblyの生成_型から取得
C07_すべての型を取得する
C08_publicな型だけを取得する
C09_名前を指定して型を取得する
C10_メタ情報の取得
インスタンスの生成
D01_インスタンスの生成
D02_インスタンス生成後の具体例
D03_空のコンストラクタがないとエラーになる
D04_Activatorを使ったインスタンスの生成
D05_引数ありのコンストラクタで生成する方法
ConstructorInfo
E01_コンストラクタの情報を取得
E02_パラメータの情報を取得
E03_コンストラクタの情報からインスタンスを生成する
E04_コンストラクタを検索して生成する
E05_public以外のコンストラクタを取得
E06_staticなコンストラクタがある場合の注意点
E07_コンストラクタのアクセスレベルを取得する
MethodInfo
F01_MethodInfo
F02_引数ありのメソッド呼び出し
F03_メソッドがオーバーロードされているときの指定方法
F04_GetMethods
F05_属性で取得する
F06_staticメソッドを呼び出す
F07_戻り値を取得する
PropertyInfo
G01_PropertyInfo
G02_GetProperties
G03_属性を使ったチェック
G04_汎用的なチェックメソッド
G05_CanWrite
FieldInfo
H01_FieldInfo
H02_public以外のFieldInfo
EventInfo
J01_EventInfoとは
J02_AddEventHandler
J03_EventInfoの注意点
J04_RemoveEventHandler
dynamicを利用したリフレクション
K01_dynamicを利用したリフレクション
K02_dynamicを利用したメソッドの呼び方
K03_オーバーライドされたメソッドの呼び出し
K04_メソッドの存在チェック
K05_dynamicを利用したプロパティへのアクセス
K06_dynamicを利用したフィールドへのアクセス
K07_dynamicを利用したイベントへのアクセス