自作の属性の出番はどんなとき?【C#,attribute】

クラスやプロパティが違うので、同じような処理を何回も書かないといけない場面に
遭遇したことはないでしょうか。

今回は、クラス・プロパティ・型を超えて処理を共通化したい

とお悩みの方向けに、ひとつ方法を提案したいと思います。

それは属性の利用です。
「自作の属性の定義方法」
「属性の利用の仕方」
「属性が有効な理由(メタで共通化と分離)」

を通して、自前で属性を利用する方法と、効果的な場面を

紹介する内容となっています。

属性に興味のある方は、本文を読んでいただければと思います。

そもそも属性とは

最初に属性とはなにか、簡単に確認しましょう。

クラスやプロパティに[○○]の形でつけることができるメタ情報を追加する仕組みです。

属性をつかうと、属性によって処理を変えることができます。

例えば、クラスの型、プロパティの型に関係なく処理を共通化したりできます。

つまり、メタでの共通化ともいえます。

属性の使い方はいくつかあるのですけれども
今回はこちらに注目していきます。

属性の定義の仕方

続いて属性の定義のしかたです。

自作属性はAttibuteを継承して作成


Attibuteという、その名の通り属性クラスを継承して作ります。

作るクラスは、慣例により、属性名はすべて “Attribute” という単語で終わらせるとよいでしょう。

利用者が見て属性だとわかるので、有用です。

定義した属性の使い方:使うときは末尾のAttibuteは省略

改めてになりますが、クラスやプロパティに[○○]の形で属性は利用できます。

ちなみに使う側は、Attrubuteとわかって使うので、

慣例で付与していたAttibuteは省略OKです。

もちろんAttibuteを付与して書くこともできますが、

VisualStudio上でも省略可能ということで、文字色が薄く表記されますので、基本省略して大丈夫です。

属性に引数をとる定義

属性には、属性を付与するときに引数を用意できます。

この引数の定義は、コンストラクタで行います。


このコンストラクタ引数が、「MyCustom」で使用するときの引数に相当します。

定義した属性の利用方法

定義した属性の利用方法は3段階です。

  1. 対象に属性を付与する
  2. メタ情報にアクセスする
  3. 属性を取り出す
  4. 属性固有の処理

C#やNuGet経由で用意されている既存属性は、属性固有の処理もいっしょについてくるので、
自前で処理を書く必要はありません。

しかし、自分で定義した属性の処理は、自分で記述していくことになります。

「対象に属性を付与する」は定義した属性の使い方で、
ふれましたので、残りの項目に注目しましょう。

使用するコードはこちらです。


プロパティ情報にアクセスする

属性を使った処理をするためには、属性データにアクセスしなければなりません。

属性にアクセスする最初のステップは、
クラスやプロパティの情報にアクセスするところから始まります。


今回はプロパティの情報にアクセスする例を紹介します。

生成したクラスのインスタンスからGetType()をしたあとGetPropertyで対象のプロパティを指定するだけで、プロパティ情報を取得できます。

そして、今回はプロパティを例にとっていますが、
ほかにもクラス、メソッドなどの情報にもアクセスできます。

詳しくは補足をご覧ください。

補足です。

情報のアクセスには派生があります。
https://docs.microsoft.com/ja-jp/dotnet/api/system.reflection.memberinfo?view=net-5.0

クラスの情報にアクセスしたいならGetType()
プロパティの情報にだけアクセスしたいならGetProperty()
メソッドの情報にアクセスしたいならGetMethod()
フィールドの情報にアクセスしたいならGetField()
プロパティ、メソッドなど問わずアクセスしたいならGetMember()

属性を取り出して処理する

では、続きのコードが何をしているのかを説明します。


Attribute.GetCustomAttribute(propertyInfo, typeof(MyCustomAttribute));
で自作した属性を取り出します。

isで変換できるかチェックして、変換できるなら

MyCustomAttribute myCustomAttribute = attribute as MyCustomAttribute;

で明示的にキャストしています。

これで自作属性の一連の流れは終わりです。

あとは、この自作属性を効果的に使えるかどうかにふれていきます。

自作属性の使いどころ

属性は、ただのラベルではありません。

プロパティやクラスを超えて処理を超えての処理が可能です。

どのような時に属性が活躍するのか既存の属性を例に確認していきましょう。

  • メタでの共通化
  • メタでの分離
  • 自作属性の一例:フォーマット

メタでの共通化

文字列型、数値型、日付型、異なる型で同じ処理をさせたい場合も属性は有効です。

あるいは、メソッドやプロパティも超えて共通化できます。

異なる型で共通化

例えば、HTTPリクエストで必須を判定する属性

[Required]

をみてみましょう。


[Required]は必須をチェックし、Nullのときバリデーションを不合格にできる、という属性です。

今回の例では、[Required]が付与されているすべてのプロパティ、
文字列型、数値型、日付型、オブジェクトを問わず必須チェックされることになります。

メソッドやプロパティを超えて共通化

他にも、メソッドやプロパティを共通化する例もあります
Obsoleteという属性は、機能を廃止するための属性です。


それどころか、メソッドやプロパティに関係なく付与することができます

確かに、最終的にプログラム上から削除するのに、
クラス、メソッド、プロパティには関係なく「廃止」を付与できないと困りますね。

もし型で共通化の処理ができるのであれば、インターフェースや継承を駆使したほうが確実です。

しかしながら、今回紹介した必須や廃止は、
型あるいは、メソッドやプロパティを超えて利用したい内容ですね。

メタでの分離

属性を利用するとメタでの機能分離が可能です。

共通インターフェースだからといって、すべての動きを統一したいかとは限りません。

同じ型や派生元が同じなのに別の動きにしたい場合も属性が活躍します。

先ほどの[Required]を使って「メタでの分離」をみてみましょう。


もし文字列型を必須にしたいのであれば、型にそういう機能を付ければよいだけです。

しかし、文字列型すべてが必須パラメータになるとはかいぎりません。

このように同じ型、同じ派生元であっても処理を分離したいときにも属性の出番になります。

自作属性の一例:フォーマット

実際に属性を使ってみた例として、フォーマットを紹介します。

0埋めとか、全角スペース埋めなどの帳票データがあったので使ったことがあります。

上の画像はそのときのイメージです。

昔ながらのデータ形式では、たまに方眼紙のようなデータ出力をしているものがあるのですが、

int,double,Datetime,string,etc. を統一のフォーマットにするのに、
いちいちToStringするより効果的だろうと、属性を利用しました。

これがプロパティが10,20と少ないうちは、属性利用を使うか悩んだのですけれども、

対象のプロパティの数が100とか超えてくると、プロパティのごとに変換処理を書くより
属性付与のほうがコスパがよいので採用した経緯があります。

また、プロパティと一緒に属性に出力形式があったほうが、
情報としてはひとまとまりになり、一目でわかるメリットもありましたね。

まとめ

  • 属性を使うとメタ情報で処理を共通化あるいは分離できる

今回は自分で定義する属性についてふれました。

属性自体はコンパイル時に利用されたり、使い方の幅はたくさんあります。

Obsoleteはコンパイルエラーを発生させることができる属性でしたね。
C#ですでに定義してある属性は、様々なメタ処理を行えます。

Obsoleteは別記事でも紹介していますので、興味があればご覧ください。

メソッドやプロパティを削除するための方法【Obsolete】

自分で属性をつくるにしろ、既存の属性を利用するにしろ、
属性を活躍できる場面があれば、いろいろ使ってみたいですね。

では。