最初に
本記事はC#言語機能におけるカスタム属性
について調べたものとなります。
サンプルコードの実況環境に関してはMono 5.16.0(C# 7.3サポート)となっております。
属性とは?
属性とはprivate
やpublic
のような追加情報をクラスやメンバに与える機能を指します。
カスタム属性とは?
System.Attribute
を継承するクラスを作成することで、private
やpublic
のような属性を用意できるC#の機能です。カスタム属性はクラスやメンバに対して自由に追加することができます。また、この機能は、リフレクションと組み合わせて使うことで非常に強力な機能を生み出すことが可能です。
自分でカスタム属性を作成することもできます。定義するためには、System.Attribute
を継承したpublicな属性クラスを作成する必要があります。定義時には、クラス名の最後に「Attribute」をつけることが推奨されています(実際既存のカスタムクラス群はそのような名前付けがされています)。ただし使用時には、Attribute
というサフィックスを省略することができます。
カスタム属性を使用することで以下のようなメリットがあります。
- 条件付きコンパイルのような、コンパイラへの指示を行うことができる。
- クラス利用者へ開発者が伝えたい情報をメタデータとしてプログラムに埋め込むことができる。
-
リフレクション
と組み合わせて、プログラム実行時に属性情報を取得し使用する。
カスタム属性によってはパラメータを渡すことが可能なものもあります(後述)。属性自体が引数を取らない場合は括弧の省略が可能です。
ただし以下のような注意点もあります。
- 属性はメタデータのため動的な設定は不可能。
-
リフレクション
を使用することでパフォーマンスが少々ボトルネックになるかもしれません。 - カスタム属性がインスタンス化されるタイミングはCLRによって決まり、ユーザーが管理することはできません。
属性の基本的な使い方
カスタム属性は以下のような記述して使用することができます。
[属性名(位置指定パラメーター)]
クラスやメソッドの定義
位置指定パラメーターとは、指定した属性クラスのコンストラクタに渡される引数のことです。特定の順序で値を指定する必要があり、省略はできません。
以下の場合はOldMethodを使用した場合に、「Old version. Don't use.」という警告が表示されるよう属性をメソッドに付与しています。
// メソッドを対象に属性付与
// Obsoleteの説明は後述
[Obsolete("Old version. Don't use.")]
public void OldMethod()
{
// 何らかの処理
}
複数の属性を指定することも可能です。その場合は,
で区切ります。
[属性名(位置指定パラメータ), 属性名(位置指定パラメーター)]
クラスやメソッドの定義
// Conditionalの説明も後述
[Conditional("DEBUG"), Obsolete("Old version. Don't use.")]
public void OldMethod()
{
// 何らかの処理
}
属性クラスによっては、クラス内でpublic
と指定されているフィールドやプロパティに対して値を設定することができます。
[属性名(位置指定パラメータ, 名前付きパラメーター = 値)]
クラスやメソッドの定義
この値を設定するためにフィールドやプロパティ名を指定する箇所を名前付きパラメーターと呼びます。
// StringLengthの説明も後述
[StringLength(15, ErrorMessage = "会社名は15文字以内で記述してください。")]
public String OfficeName
標準ライブラリから提供される基本的なカスタム属性の種類
コンパイラやVisual Studioに対して指示を行える便利な属性がいくつか標準ライブラリによって提供されています。これらは名前の後にAttribute
というサフィックスが入っていますが、使用時には省略することができます。
属性名 | 概要 |
---|---|
[AttributeUsage] | 属性クラスを自作した場合に使用します(後述)。属性の用途を指定します。 |
[Conditional] | 指定のシンボルが定義されている場合のみ属性対象のメソッド(又は属性)を呼び出すように指定します。 |
[CLSCompliant] | CLSへの準拠を検証するかどうか指定します。 |
[Obsolete] | 属性対象のメソッド等が古いバージョンのため使用が推奨されないことを表します(使用した場合警告が発せられます)。 |
バリデーションに使用する属性として有用なものはSystem.ComponentModel.DataAnnotations
下に存在しており、Validator
クラスを使用することで、対象データの妥当性を検証することができます。
(バリデーションってなんだ?という方は「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典「バリデーション(validation)」へ)
属性名 | 概要 |
---|---|
[Required] | 属性対象のデータに対してnullや空文字を許可しないことを示します。 |
[Range] | 属性対象のデータの値の範囲を指定します。 |
[StringLength] | 属性対象の文字列データに対して最大長/最小長を指定します。 |
[Required(ErrorMessage="入力必須項目です。"), StringLength(20, ErrorMessage="名前は20桁以内で入力してください")]
String OfficeName;
Unityから提供される基本的なカスタム属性の種類
エディタ拡張系
属性名 | 概要 |
---|---|
[SerializeField] | 属性対象がシリアライズ可能であることを表します。 |
[NonSerialized] | 対象がシリアライズ不可能であることを表します。 |
[Tooltip] | マウスカーソルがフィールドの上に置かれた場合に位置指定パラメーターとして渡された値を表示します。 |
[Space] | フィールド間に位置指定パラメーターとして渡された値分スペースを設定します。 |
[Header] | フィールドの上に位置指定パラメーターとして渡された文字をタイトルとして表示します。 |
[Multiline] | 位置指定パラメーターとして渡された値分の行数を入力することができるテキストフィールドを設定します。 |
[TextArea] | 複数行の入力が可能なテキストフィールドを設定します。位置指定パラメーターで、行数のMIN値・MAX値が設定可能です。 |
[HideInInspector] | public等のシリアライズするフィールドをInspectorに表示しないように設定できます。 |
[FormerlySerializedAs] | 変数名を変更した際に変数が持っている情報を変数名変更後の変数に引き継がれ、保持するよう設定できます。 |
コンポーネント系
属性名 | 概要 |
---|---|
[RequireComponent] | 位置指定パラメーターとして渡された(例えばtypeof(Rigidbody) )コンポーネントがアタッチされていなければ追加します。 |
[DisallowMultipleComponent] | 対象のオブジェクトに対して複数のコンポーネントの追加を設定不可能にします。 |
[ExecuteInEditMode] | コンポーネントのStartやUpdateメソッドをゲームスタートしていない状態でも確認できるようになります。 |
C++との連携系
属性名 | 概要 |
---|---|
MonoPInvokeCallback | C#のメソッドをC++から呼び出せるようにします。 |
DLLImport | C++のメソッドをC#から呼び出せるようにします。 |
属性対象の指定
基本的に属性の対象となるのは、その直後に続くクラスやメソッド、フィールドとなります。しかし、明示的に属性の対象を変更することができます。
[属性対象: 属性名(位置指定パラメーター等...)]
属性対象としては以下のものを指定できます。
- assembly
- module
- type (クラス,構造体,列挙型,デリゲート等の型が対象)
- method
- property
- field
- param
- event
- return
特にプロパティやイベントでは主にmethod
やparam
、field
辺りを使うこととなります。
class Test
{
public int Prop
{
// getメソッドに対応
[method: CustomAttribute]
// getの返却値に対応
[return: Required]
get => 10;
// setメソッドに対応
[method: CustomAttribute]
// setが受け取るvalue引数に対応
[param: Required]
set { }
}
// バッキングフィールドを対象とする
[field: CustomAttribute]
public int AutoProp { get; }
}
独自のカスタムクラス
標準ライブラリで提供されているような属性はSystem.Attribute
を継承したクラス、又はその派生クラスを継承したクラスです。つまり同様に継承を行うことで自分自身でも独自の属性クラスを作成することができます。
作成する属性クラスにプロパティが含まれている場合はプロパティは読み取り/書き込み可能にしておく必要があります。
ここで重要となるのは、作成する属性クラスの振る舞いを決めるAttributeUsage
属性です。この属性の位置指定パラメーターであるAttributeTargets
と名前付きパラメーターであるAllowMultiple
とInherited
を確認してみましょう。
「AttributeTargets」
属性を適用できる範囲を指定できます。
例えばAttributeTargets.Classを設定することで、クラスに対して属性を設定可能、とできます。AttributeTargets.Allであれば全ての要素に対して適用が可能です。また、複数のAttributeTargetsの値を渡すことも可能です。
「AllowMultiple」
作成した属性をターゲットに対して複数回適用が可能かどうかをbool値で指定します。false
であれば一度のみとなります。デフォルト値はfalse
です。
「Inherited」
作成した属性のターゲットが継承された後も、そのターゲットに設定された属性が一緒に継承されるかどうかをbool値で指定します。デフォルト値はtrue
です。
上記を踏まえてサンプルコードを見てみましょう
// この場合はクラスと構造体に対し属性の設定が可能で、複数回付与でき、属性のターゲットが継承時には属性は引き継がれません。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct,
AllowMultiple = true, Inherited = false)]
public class DeveloperAttribute : System.Attribute
{
// 作者名
private string Name;
// 対象が動作するバージョン
public float Version;
public AuthorAttribute(string name) { Name = name; }
}
最後に
如何でしたでしょうか。一口に属性と言っても調べてみるととても奥が深い機能であるということが伝わったかと思います。属性機能をどんどん活用して楽しいC#ライフを送りましょう。