リフレクション

C#文法 リフレクション G03_属性を使ったチェック

属性(Attribute)を使ったチェックとは

前回はGetPropertiesを使って、クラスの中にあるプロパティの一覧を取得する方法を扱いました。今回はそれに加えて、プロパティに付けられた「属性(Attribute)」をもとに、特定の属性が付いたプロパティだけを取り出してチェックする方法を紹介します。

たとえばEntity Frameworkでは、キー項目に[Key]を付けたり、[MaxLength(50)]で文字数の上限を指定したり、必須項目には[Required]を付けたりします。ツール側は、こうした属性の定義を読み取ることでチェックを行っています。今回はその仕組みを、自分たちのコードで再現してみます。

図1 StockEntityクラスに設定できる属性([Key]・[MaxLength(50)]など)

StockEntityクラスに属性を設定する

属性はプロパティの前に角括弧[ ]を付けて指定します。以前メソッドの回で扱ったように、Attributeを継承したクラスを作れば独自の属性を定義できますが、今回はマイクロソフトが標準で提供している属性を使います。System.ComponentModel.DataAnnotations名前空間には、MaxLengthをはじめとする便利な属性が用意されています。

図2 System.ComponentModel名前空間の候補からMaxLengthを選択する

名前空間が長いのでusingに移動しておき、MaxLengthの括弧の中に最大文字数を指定します。今回は説明用に短めの5文字に設定しました。

public int StockId { get; set; } = 999;

[MaxLength(5)]

public string StockName { get; set; }

図3 StockNameプロパティに[MaxLength(5)]を設定したコード

この状態で前回作成した「GetPropertiesで全プロパティを取得する」コードをそのまま流用して実行すると、以前と同じくStockIdとStockNameの2つのプロパティが取得できます。ここからさらに、属性ごとのチェックを追加していきます。

GetCustomAttributeで属性を取得する

GetPropertiesでループしているvalという1つ1つのプロパティに対して、GetCustomAttribute<T>()を呼び出します。山括弧の中に調べたい属性の型を指定すると、そのプロパティにその属性が付いていればインスタンスが返り、付いていなければnullが返ってきます。

なお、属性クラスの正式名はMaxLengthAttributeのように語尾に「Attribute」が付きますが、山括弧の中では語尾のAttributeを省略して書くことができます。エディタ上でも省略できる部分は文字が薄く表示され、直感的に書けるようになっています。

図4 val.GetCustomAttribute<MaxLengthAttribute>()で属性を取得するコード

var maxLength = val.GetCustomAttribute<MaxLengthAttribute>();

if (maxLength != null)

{

// MaxLengthが設定されているプロパティのみここに入る

}

プロパティの値と属性の定義を比較する

MaxLengthAttributeが取得できたら、次はチェック対象のインスタンスから実際の値を取り出します。インスタンスはActivator.CreateInstanceで生成し、val.GetValue(instance)に渡すことで、そのプロパティの値をobject型で取得できます。値がnullでなければ、文字列に変換した上で長さを比較します。

図5 val.GetValue(instance)でプロパティの値を取得するコード

取得した値の文字数(o.ToString().Length)と、属性側の定義(maxLength.Length)を比較し、値のほうが大きければ上限オーバーということになります。今回は説明のために、通常のExceptionを使って上限オーバーのメッセージを投げるようにしました。

図6 文字数を比較し、上限を超えていればExceptionを投げるコード

if (o.ToString().Length > maxLength.Length)

{

throw new Exception($”オーバーしています {o.ToString().Length} 上限:{maxLength.Length}”);

}

実行結果とtry-catchでの例外処理

StockNameに6文字の値を設定して実行すると、5文字の上限を超えるため例外が発生します。try-catchで囲っていない状態で実行すると、下図のように未処理の例外としてダイアログが表示され、「オーバーしています 6 上限:5」というメッセージが確認できます。

図7 StockNameが6文字のとき、上限5文字を超えて例外が発生した画面

実際の利用場面ではこのまま落とすのではなく、try-catchで例外を捕まえて処理するのが一般的です。catchブロックでMessageBox.Showに例外情報を渡してあげれば、画面上にメッセージとして表示できます。

図8 catch(Exception ex)でMessageBox.Show(ex.ToString())を呼び出すコード

try

{

// 属性チェックの処理

}

catch (Exception ex)

{

MessageBox.Show(ex.ToString());

}

まとめ

1つのプロパティに対してGetCustomAttribute<T>()を呼び出せば、そのプロパティに指定した属性が付いているかどうか、また付いている場合はその定義内容を取得できます。取得した定義とGetValueで取れたインスタンスの実際の値を比較すれば、必須チェックや範囲チェックなど、さまざまな検証ロジックを組み立てることができます。

Entity Frameworkなどのツールが「この属性を付けてください」と説明するとき、裏側ではまさに今回のように、属性の定義を読み取ってチェックを行っています。プロパティの属性を活用したチェックの仕組みを、ぜひ自分のコードでも試してみてください。

リフレクション

はじめに
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を利用したイベントへのアクセス