C#属性とは?Attributeの基本・使い方・プロパティとの違いを初心者向けに解説
はじめに
C#を学び始めると、クラスやメソッドの上に[Obsolete]や[Serializable]、[Required]のような角括弧付きの記述を見かけることがあります。これがC#の「属性」、英語では「Attribute」と呼ばれる機能です。
C#属性は、コードそのものに追加情報を付けるための仕組みです。見た目はシンプルですが、ASP.NET、Entity Framework、テストフレームワーク、シリアライズ、バリデーションなど、実際の開発では非常によく使われます。
一方で、初心者にとっては「プロパティと何が違うの?」「属性を付けると何が起きるの?」「Attributeって省略できるの?」と混乱しやすいポイントも多いです。
この記事では、C#属性とは何か、基本的な使い方、プロパティとの違い、標準属性の例、カスタム属性の作り方、リフレクションで属性を取得する方法まで、初心者にもわかりやすく解説します。
1. C#の属性(Attribute)とは?
1-1. 属性はクラス・メソッド・プロパティなどに追加情報を付ける仕組み
C#の属性とは、クラス、メソッド、プロパティ、フィールド、引数、戻り値などに対して、追加の情報を付けるための仕組みです。
たとえば、次のようなコードがあります。
C#[Obsolete]
public void OldMethod()
{
Console.WriteLine("古いメソッドです");
}
[Obsolete]が属性です。
この属性を付けることで、「このメソッドは古いものです」という情報をコンパイラや開発者に伝えることができます。
属性は、処理の本体を書くものではありません。コード要素に対して「これはこういう意味を持っています」「このように扱ってください」という情報を付けるために使います。
1-2. Attributeは「メタデータ」をコードに付与するための機能
C#属性は、メタデータを付与するための機能です。
メタデータとは、「データに関するデータ」のことです。プログラムそのものの処理ではなく、そのコードに関する補足情報だと考えるとわかりやすいです。
たとえば、次のような情報を属性として付けられます。
C#[Serializable]
public class User
{
public string Name { get; set; }
}
この[Serializable]は、「このクラスはシリアライズ可能である」という情報を表します。
つまり属性は、クラスやメソッドに対して「これはどういう性質を持つものなのか」を説明するラベルのようなものです。
1-3. 属性はプログラムの動作を直接書くものではなく、情報を渡すために使う
C#属性で初心者が特に注意したいのは、属性を付けただけで必ず何かの処理が実行されるわけではないという点です。
たとえば、次のようなカスタム属性を作ったとします。
C#[Important]
public class Report
{
}
この[Important]という属性を付けただけでは、自動的に何か特別な処理が実行されるわけではありません。
属性はあくまで情報です。その情報をコンパイラ、フレームワーク、ライブラリ、または自分で書いたプログラムが読み取って、必要な処理を行います。
つまり、属性は「命令」ではなく「目印」や「設定情報」に近いものです。
1-4. C#で属性が使われる代表的な場面
C#属性は、さまざまな場面で使われます。
代表的な例としては、古いメソッドに警告を出すObsolete属性、シリアライズ対象を示すSerializable属性、入力チェックを行うRequiredやStringLengthなどのバリデーション属性があります。
また、ASP.NETではルーティングやAPIの設定に属性がよく使われます。
C#[HttpGet]
public IActionResult Get()
{
return Ok();
}
Entity Frameworkでは、データベースのテーブル名やキー項目を指定するために属性が使われます。
C#[Key]
public int Id { get; set; }
このようにC#属性は、コードに対して補足情報を宣言的に付けるため、多くのフレームワークで活用されています。
2. C#の属性とプロパティの違い
2-1. 「属性」と「プロパティ」は名前が似ているが別物
C#を学習していると、「属性」と「プロパティ」という言葉が出てきます。日本語ではどちらも似た意味に感じるため、初心者が混同しやすいポイントです。
しかし、C#における属性とプロパティはまったく別のものです。
属性はAttributeのことで、コード要素にメタデータを付ける仕組みです。一方、プロパティはクラスや構造体が持つデータを読み書きするためのメンバーです。
2-2. プロパティはオブジェクトの値を扱うメンバー
プロパティは、オブジェクトが持つ値を扱うための仕組みです。
C#public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
この例では、NameとAgeがプロパティです。
プロパティを使うことで、オブジェクトの値を取得したり設定したりできます。
C#var user = new User();
user.Name = "田中";
user.Age = 25;
Console.WriteLine(user.Name);
プロパティは、実際のデータを扱うためのクラスのメンバーです。
2-3. 属性(Attribute)はコード要素に付ける補足情報
属性は、クラスやメソッド、プロパティなどに付ける補足情報です。
C#public class User
{
[Required]
public string Name { get; set; }
}
この例では、Nameがプロパティで、[Required]が属性です。
Nameはユーザー名という値を扱います。一方、[Required]は「このプロパティは必須項目です」という情報を付けています。
つまり、プロパティは値を持つもの、属性はそのプロパティなどに意味やルールを付けるものです。
2-4. プロパティに属性を付ける例で違いを理解する
次のコードを見ると、属性とプロパティの違いがわかりやすくなります。
C#public class Product
{
[Required]
[StringLength(50)]
public string Name { get; set; }
[Range(1, 100000)]
public int Price { get; set; }
}
このコードでは、NameとPriceがプロパティです。
一方、[Required]、[StringLength(50)]、[Range(1, 100000)]が属性です。
Nameプロパティには、「必須」「最大50文字」という情報が付けられています。Priceプロパティには、「1以上100000以下」という情報が付けられています。
このように、プロパティは値を表し、属性はその値に対するルールや補足情報を表します。
2-5. 初心者が混同しやすいポイント
初心者が混同しやすいのは、「プロパティにも属性という言葉が使われる」点です。
日常的な日本語では、「商品の属性」「ユーザーの属性」のように、性質や特徴を属性と呼びます。しかしC#でAttributeと言う場合は、角括弧で付ける特別な仕組みを指します。
整理すると、次のようになります。
| 用語 | C#での意味 |
|---|---|
| 属性 | Attribute。コード要素に付けるメタデータ |
| プロパティ | クラスが持つ値を扱うメンバー |
C#属性を理解するときは、「属性は角括弧で付ける補足情報」と覚えるとわかりやすいです。
3. C#属性の基本的な書き方
3-1. 属性は角括弧[]を使って記述する
C#属性は、角括弧[]を使って記述します。
C#[属性名]
対象のコード
たとえば、メソッドにObsolete属性を付ける場合は次のように書きます。
C#[Obsolete]
public void OldMethod()
{
}
属性は、対象となるクラス、メソッド、プロパティなどの直前に書くのが基本です。
3-2. 属性をクラスに付ける書き方
クラスに属性を付ける場合は、クラス定義の直前に記述します。
C#[Serializable]
public class User
{
public string Name { get; set; }
}
この例では、UserクラスにSerializable属性を付けています。
クラス全体に関する情報を表したい場合は、このようにクラスに属性を付けます。
3-3. 属性をメソッドに付ける書き方
メソッドに属性を付ける場合は、メソッド定義の直前に記述します。
C#[Obsolete]
public void OldMethod()
{
Console.WriteLine("古いメソッドです");
}
この例では、OldMethodメソッドにObsolete属性を付けています。
この属性により、このメソッドを使ったときに警告を出すことができます。
3-4. 属性をプロパティに付ける書き方
プロパティに属性を付ける場合は、プロパティ定義の直前に記述します。
C#public class User
{
[Required]
public string Name { get; set; }
}
この例では、NameプロパティにRequired属性を付けています。
フォーム入力やAPIのリクエストモデルなどでは、プロパティにバリデーション属性を付ける場面がよくあります。
3-5. Attributeという接尾辞は省略できる
C#の属性クラスは、通常〇〇Attributeという名前で定義されます。
たとえば、Obsolete属性の正式なクラス名はObsoleteAttributeです。しかし、属性として使うときは末尾のAttributeを省略できます。
C#[Obsolete]
public void OldMethod()
{
}
これは次のように書くこともできます。
C#[ObsoleteAttribute]
public void OldMethod()
{
}
どちらも同じ意味です。
ただし、一般的にはAttributeを省略して書くことが多いです。
3-6. 属性に引数を渡す書き方
属性には、引数を渡すこともできます。
C#[Obsolete("このメソッドは古いです。NewMethodを使ってください。")]
public void OldMethod()
{
}
この例では、Obsolete属性にメッセージを渡しています。
また、複数の引数を渡すこともできます。
C#[Obsolete("このメソッドは使用しないでください。", true)]
public void OldMethod()
{
}
第2引数にtrueを指定すると、このメソッドを使ったときに警告ではなくコンパイルエラーになります。
属性の引数には、コンストラクタ引数と名前付き引数があります。名前付き引数については、後のサンプルで詳しく解説します。
4. よく使われるC#標準属性の例
4-1. Obsolete属性:古いメソッドやクラスに警告を出す
Obsolete属性は、古くなったクラスやメソッドに対して警告を出すための属性です。
C#[Obsolete("このメソッドは古いです。NewMethodを使ってください。")]
public void OldMethod()
{
}
このメソッドを呼び出すと、コンパイラが警告を表示します。
C#OldMethod();
ライブラリやアプリケーションを長く運用していると、古いメソッドをすぐに削除できないことがあります。そのような場合にObsolete属性を付けておくと、利用者に新しいメソッドへの移行を促すことができます。
4-2. Serializable属性:シリアライズ対象であることを示す
Serializable属性は、クラスがシリアライズ可能であることを示す属性です。
C#[Serializable]
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
シリアライズとは、オブジェクトを保存や送信に適した形式に変換することです。
たとえば、オブジェクトをファイルに保存したり、ネットワーク越しに送信したりするときに使われます。
現在のC#開発ではJSONシリアライズを使うことも多いため、すべての場面でSerializable属性が必要になるわけではありません。ただし、属性の代表例として覚えておくとよいでしょう。
4-3. Conditional属性:条件付きでメソッド呼び出しを有効にする
Conditional属性は、指定したコンパイルシンボルが定義されている場合だけ、メソッド呼び出しを有効にする属性です。
C#using System.Diagnostics;
public class Logger
{
[Conditional("DEBUG")]
public void DebugLog(string message)
{
Console.WriteLine(message);
}
}
この例では、DEBUGシンボルが定義されている場合だけ、DebugLogメソッドの呼び出しが有効になります。
デバッグ時だけログを出したい場合などに使われます。
4-4. Required・StringLengthなどバリデーション系属性
C#では、入力値の検証に使われる属性もよく登場します。
代表的なものに、Required、StringLength、Rangeなどがあります。
C#using System.ComponentModel.DataAnnotations;
public class User
{
[Required]
public string Name { get; set; }
[StringLength(100)]
public string Email { get; set; }
[Range(0, 120)]
public int Age { get; set; }
}
Requiredは必須項目、StringLengthは文字数制限、Rangeは数値の範囲を表します。
ASP.NET Coreのモデルバリデーションなどでは、これらの属性を使って入力チェックを行うことができます。
4-5. ASP.NETやEntity Frameworkで使われる属性の例
ASP.NETでは、コントローラーやアクションメソッドに属性を付けて、HTTPメソッドやルーティングを指定することがあります。
C#[Route("api/users")]
public class UsersController
{
[HttpGet]
public string Get()
{
return "user list";
}
}
この例では、[Route]でURLのパターンを指定し、[HttpGet]でGETリクエストに対応するメソッドであることを示しています。
Entity Frameworkでは、データベースとの対応関係を属性で指定することがあります。
C#using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
[Table("Users")]
public class User
{
[Key]
public int Id { get; set; }
[Column("user_name")]
public string Name { get; set; }
}
このように、フレームワークに対して設定情報を伝えるために属性がよく使われます。
4-6. 属性を使うとコードがどう読みやすくなるのか
属性を使うと、コードの意図を宣言的に表現できます。
たとえば、次のようなコードがあるとします。
C#[Required]
[StringLength(50)]
public string Name { get; set; }
このコードを見るだけで、Nameは必須で、最大50文字までということがわかります。
別の設定ファイルや長い条件分岐を読まなくても、プロパティの近くにルールが書かれているため、コードの意味を理解しやすくなります。
属性は、プログラムの設定やルールをコードの近くに置ける点が大きなメリットです。
5. C#属性の使い方をサンプルコードで理解する
5-1. Obsolete属性を使って警告を表示する
まずは、Obsolete属性の基本例を見てみましょう。
C#public class Sample
{
[Obsolete("OldMethodは古いメソッドです。NewMethodを使ってください。")]
public void OldMethod()
{
Console.WriteLine("Old");
}
public void NewMethod()
{
Console.WriteLine("New");
}
}
このクラスを次のように使います。
C#var sample = new Sample();
sample.OldMethod();
OldMethodを呼び出すと、コンパイル時に警告が表示されます。
警告メッセージには、属性に指定した文字列が表示されます。これにより、開発者に対して「このメソッドは使わない方がよい」という情報を伝えられます。
5-2. プロパティにRequired属性を付ける
次に、プロパティにRequired属性を付ける例です。
C#using System.ComponentModel.DataAnnotations;
public class User
{
[Required]
public string Name { get; set; }
}
このコードでは、Nameプロパティが必須項目であることを表しています。
ASP.NET Coreなどでは、このようなモデルに対してバリデーションを行うことで、Nameが未入力の場合にエラーとして扱うことができます。
ただし、[Required]を付けただけで、通常のコンソールアプリなどで自動的にエラーになるわけではありません。属性を読み取って検証する仕組みが必要です。
5-3. 複数の属性を同時に付ける
1つのコード要素に複数の属性を付けることもできます。
C#using System.ComponentModel.DataAnnotations;
public class User
{
[Required]
[StringLength(50)]
public string Name { get; set; }
}
また、同じ角括弧の中にカンマ区切りで書くこともできます。
C#public class User
{
[Required, StringLength(50)]
public string Name { get; set; }
}
どちらの書き方でも意味は同じです。
ただし、属性が多い場合は、1行ずつ分けて書いた方が読みやすいことが多いです。
5-4. 属性に名前付き引数を指定する
属性には、名前付き引数を指定できます。
C#using System.ComponentModel.DataAnnotations;
public class User
{
[StringLength(50, MinimumLength = 3)]
public string Name { get; set; }
}
この例では、StringLength属性に最大文字数50を渡し、さらにMinimumLength = 3で最小文字数を指定しています。
このように、属性では必須の値をコンストラクタ引数で渡し、任意の設定を名前付き引数で指定することがよくあります。
5-5. 属性が付いたコードの読み方
属性が付いたコードを読むときは、まず「どの対象に属性が付いているか」を確認します。
C#[Obsolete]
public class OldClass
{
}
この場合、Obsolete属性はOldClassクラスに付いています。
C#public class User
{
[Required]
public string Name { get; set; }
}
この場合、Required属性はNameプロパティに付いています。
C#public class Sample
{
[Obsolete]
public void OldMethod()
{
}
}
この場合、Obsolete属性はOldMethodメソッドに付いています。
属性を読むときは、「属性名」「引数」「付いている対象」の3つを確認すると理解しやすくなります。
6. カスタム属性の作り方
6-1. 独自の属性クラスを作る流れ
C#では、自分で独自の属性を作ることもできます。これをカスタム属性と呼びます。
カスタム属性を作る基本的な流れは次のとおりです。
Attributeクラスを継承するクラス名を
〇〇Attributeにする必須情報はコンストラクタ引数で受け取る
任意情報はプロパティで設定できるようにする
クラスやプロパティなどに付ける
必要に応じてリフレクションで取得する
6-2. Attributeクラスを継承する
カスタム属性は、System.Attributeクラスを継承して作ります。
C#public class DescriptionAttribute : Attribute
{
}
これで、DescriptionAttributeという独自の属性クラスが作成できます。
属性として使う場合は、Attributeを省略して次のように書けます。
C#[Description]
public class User
{
}
6-3. クラス名は〇〇Attributeにするのが基本
カスタム属性のクラス名は、〇〇Attributeという名前にするのが一般的です。
たとえば、画面表示名を表す属性なら、次のように作れます。
C#public class DisplayNameAttribute : Attribute
{
}
使用するときは、次のようにAttributeを省略できます。
C#[DisplayName]
public class Product
{
}
この命名ルールに従うことで、C#の属性として自然に扱えます。
6-4. コンストラクタ引数で必須情報を受け取る
属性に必須情報を持たせたい場合は、コンストラクタ引数を使います。
C#public class LabelAttribute : Attribute
{
public string Text { get; }
public LabelAttribute(string text)
{
Text = text;
}
}
この属性は、次のように使えます。
C#[Label("商品名")]
public string Name { get; set; }
"商品名"はコンストラクタ引数として渡され、Textプロパティに保存されます。
6-5. プロパティで任意情報を設定する
任意で指定したい情報は、属性クラスのプロパティとして定義します。
C#public class LabelAttribute : Attribute
{
public string Text { get; }
public int Order { get; set; }
public LabelAttribute(string text)
{
Text = text;
}
}
この属性は、次のように使えます。
C#[Label("商品名", Order = 1)]
public string Name { get; set; }
Textは必須情報、Orderは任意情報です。
属性を設計するときは、必ず指定してほしい情報をコンストラクタ引数にし、必要に応じて指定する情報をプロパティにするとわかりやすくなります。
6-6. カスタム属性をクラスやプロパティに付ける
実際にカスタム属性を使う例を見てみましょう。
C#public class LabelAttribute : Attribute
{
public string Text { get; }
public int Order { get; set; }
public LabelAttribute(string text)
{
Text = text;
}
}
public class Product
{
[Label("商品ID", Order = 1)]
public int Id { get; set; }
[Label("商品名", Order = 2)]
public string Name { get; set; }
[Label("価格", Order = 3)]
public int Price { get; set; }
}
この例では、各プロパティに表示名と表示順を表す属性を付けています。
ただし、この属性を付けただけでは表示名や表示順が自動的に使われるわけではありません。後でリフレクションなどを使って属性を読み取り、その情報を利用する必要があります。
7. AttributeUsageで属性の適用先を制限する
7-1. AttributeUsageとは何か
AttributeUsageは、カスタム属性をどこに付けられるかを指定するための属性です。
たとえば、「この属性はクラスにだけ付けられる」「この属性はプロパティにだけ付けられる」といった制限を設定できます。
C#[AttributeUsage(AttributeTargets.Property)]
public class LabelAttribute : Attribute
{
public string Text { get; }
public LabelAttribute(string text)
{
Text = text;
}
}
この例では、LabelAttributeをプロパティにだけ付けられるようにしています。
7-2. AttributeTargetsで属性を付けられる対象を指定する
AttributeTargetsを使うと、属性を付けられる対象を指定できます。
代表的な指定先には、次のようなものがあります。
| 指定 | 意味 |
|---|---|
AttributeTargets.Class | クラスに付けられる |
AttributeTargets.Method | メソッドに付けられる |
AttributeTargets.Property | プロパティに付けられる |
AttributeTargets.Field | フィールドに付けられる |
AttributeTargets.Parameter | 引数に付けられる |
AttributeTargets.All | すべての対象に付けられる |
複数の対象を指定したい場合は、|で組み合わせます。
C#[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
public class LabelAttribute : Attribute
{
public string Text { get; }
public LabelAttribute(string text)
{
Text = text;
}
}
この例では、クラスとプロパティにLabelAttributeを付けられます。
7-3. AllowMultipleで同じ属性を複数付けられるか決める
AllowMultipleを使うと、同じ属性を同じ対象に複数付けられるかを指定できます。
C#[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class TagAttribute : Attribute
{
public string Name { get; }
public TagAttribute(string name)
{
Name = name;
}
}
この属性は、同じクラスに複数付けることができます。
C#[Tag("重要")]
[Tag("管理画面")]
public class AdminPage
{
}
AllowMultiple = falseの場合、同じ属性を複数付けるとコンパイルエラーになります。指定しない場合は、基本的に複数指定できないと考えておくとよいでしょう。
7-4. Inheritedで継承先に属性を引き継ぐか決める
Inheritedを使うと、属性を継承先のクラスに引き継ぐかどうかを指定できます。
C#[AttributeUsage(AttributeTargets.Class, Inherited = true)]
public class CategoryAttribute : Attribute
{
public string Name { get; }
public CategoryAttribute(string name)
{
Name = name;
}
}
次のように基底クラスに属性を付けた場合を考えます。
C#[Category("Base")]
public class BaseService
{
}
public class UserService : BaseService
{
}
Inherited = trueであれば、UserService側でも基底クラスの属性を取得できる場合があります。
ただし、属性の取得方法や対象によって挙動が変わることがあるため、継承を前提にする場合は実際に取得処理を確認することが重要です。
7-5. AttributeUsageを指定しない場合の注意点
カスタム属性を作るとき、AttributeUsageは必須ではありません。
C#public class LabelAttribute : Attribute
{
}
このように書いても属性として使えます。
ただし、どこにでも付けられる属性になってしまうと、意図しない場所に使われる可能性があります。
たとえば、プロパティ用に作った属性をクラスやメソッドに付けられてしまうと、後からコードを読んだ人が混乱するかもしれません。
カスタム属性を作るときは、できるだけAttributeUsageで適用先を明確にしておくのがおすすめです。
8. リフレクションで属性を取得する方法
8-1. 属性は付けるだけではなく取得して使う
属性は、コードに付けるだけでは意味を持たない場合があります。自分で作ったカスタム属性は、基本的に取得して使う処理とセットで考える必要があります。
属性を取得するには、リフレクションを使います。
リフレクションとは、実行時にクラス、メソッド、プロパティなどの情報を調べるための仕組みです。
たとえば、プロパティに付いている属性を調べて、表示名を取得するような使い方ができます。
8-2. GetCustomAttributeで属性を取得する
1つの属性を取得するには、GetCustomAttributeを使います。
C#using System.Reflection;
[AttributeUsage(AttributeTargets.Class)]
public class CategoryAttribute : Attribute
{
public string Name { get; }
public CategoryAttribute(string name)
{
Name = name;
}
}
[Category("サービス")]
public class UserService
{
}
この属性を取得してみます。
C#var type = typeof(UserService);
var attribute = type.GetCustomAttribute<CategoryAttribute>();
if (attribute != null)
{
Console.WriteLine(attribute.Name);
}
実行すると、サービスという値を取得できます。
このように、属性を読み取ることで、コードに付けたメタデータを実行時に利用できます。
8-3. GetCustomAttributesで複数の属性を取得する
同じ種類の属性を複数取得したい場合は、GetCustomAttributesを使います。
C#[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class TagAttribute : Attribute
{
public string Name { get; }
public TagAttribute(string name)
{
Name = name;
}
}
[Tag("重要")]
[Tag("管理画面")]
public class AdminPage
{
}
属性を取得するコードは次のようになります。
C#var type = typeof(AdminPage);
var attributes = type.GetCustomAttributes<TagAttribute>();
foreach (var attribute in attributes)
{
Console.WriteLine(attribute.Name);
}
この例では、重要と管理画面を取得できます。
複数の属性を扱う場合は、AllowMultiple = trueを設定しておく必要があります。
8-4. IsDefinedで属性が付いているか確認する
属性の内容までは不要で、「その属性が付いているかどうか」だけを確認したい場合は、IsDefinedを使えます。
C#var type = typeof(UserService);
bool hasCategory = type.IsDefined(typeof(CategoryAttribute), inherit: false);
Console.WriteLine(hasCategory);
IsDefinedは、指定した属性が付いていればtrue、付いていなければfalseを返します。
属性の値を使わない場合は、GetCustomAttributeよりも意図がわかりやすいコードになります。
8-5. プロパティに付いた属性を取得するサンプル
プロパティに付けたカスタム属性を取得する例を見てみましょう。
C#using System;
using System.Reflection;
[AttributeUsage(AttributeTargets.Property)]
public class LabelAttribute : Attribute
{
public string Text { get; }
public LabelAttribute(string text)
{
Text = text;
}
}
public class Product
{
[Label("商品名")]
public string Name { get; set; }
[Label("価格")]
public int Price { get; set; }
}
各プロパティの属性を取得するコードは次のようになります。
C#var type = typeof(Product);
var properties = type.GetProperties();
foreach (var property in properties)
{
var label = property.GetCustomAttribute<LabelAttribute>();
if (label != null)
{
Console.WriteLine($"{property.Name}: {label.Text}");
}
}
実行結果は次のようになります。
Name: 商品名
Price: 価格
このように、属性を使うと、プロパティ名とは別に表示名などの情報を付けて取得できます。
8-6. リフレクションを使うときの注意点
リフレクションは便利ですが、使いすぎには注意が必要です。
通常のメソッド呼び出しやプロパティアクセスに比べると、リフレクションは処理が重くなる場合があります。そのため、大量のデータに対して何度も属性を取得するような処理では、結果をキャッシュするなどの工夫が必要です。
また、リフレクションを多用すると、コードの流れがわかりにくくなることがあります。
属性を使うときは、「どこで属性を読み取っているのか」「読み取った情報を何に使っているのか」がわかるように設計することが大切です。
9. C#属性を使うメリット・デメリット
9-1. メリット:宣言的に情報を表現できる
C#属性の大きなメリットは、情報を宣言的に表現できることです。
たとえば、次のコードを見るだけで、Nameが必須項目であることがわかります。
C#[Required]
public string Name { get; set; }
条件分岐や設定ファイルを探さなくても、プロパティのすぐ近くにルールが書かれているため、コードの意図を理解しやすくなります。
属性は、「このコード要素はこう扱ってほしい」という情報を短く表現するのに向いています。
9-2. メリット:フレームワークやライブラリと連携しやすい
属性は、ASP.NET Core、Entity Framework、xUnit、NUnitなど、多くのフレームワークやライブラリで使われています。
たとえば、ASP.NET Coreでは次のようにルーティングを属性で指定できます。
C#[HttpGet("users")]
public IActionResult GetUsers()
{
return Ok();
}
Entity Frameworkでは、主キーやテーブル名を属性で指定できます。
C#[Key]
public int Id { get; set; }
フレームワーク側が属性を読み取って処理してくれるため、開発者はシンプルな記述で設定を表現できます。
9-3. メリット:共通処理や設定をコードに埋め込みやすい
属性を使うと、共通処理の対象をコード上でわかりやすく指定できます。
たとえば、ログ出力対象のメソッドに属性を付ける設計も考えられます。
C#[Log]
public void CreateUser()
{
}
この属性をリフレクションやAOP的な仕組みで読み取れば、[Log]が付いたメソッドだけログ出力するような処理を作ることもできます。
属性は、設定と対象を近い場所に書けるため、共通処理の対象を明確にしやすいです。
9-4. デメリット:処理の流れが見えにくくなる場合がある
属性は便利ですが、処理の流れが見えにくくなる場合があります。
たとえば、次のコードだけを見ると、[Authorize]が何をしているのかは、初心者にはすぐにはわからないかもしれません。
C#[Authorize]
public IActionResult MyPage()
{
return View();
}
実際には、フレームワークがこの属性を読み取り、認証済みユーザーだけアクセスできるように制御しています。
このように、属性によって裏側で処理が動く場合、どこで何が行われているのかを理解するにはフレームワークの仕組みも知る必要があります。
9-5. デメリット:リフレクションの使いすぎはパフォーマンスに注意
カスタム属性を取得するには、リフレクションを使うことが多いです。
リフレクションは便利ですが、通常のコードに比べると処理コストが高くなることがあります。
特に、ループの中で何度も属性を取得するようなコードは注意が必要です。
C#foreach (var item in items)
{
var attr = typeof(Product).GetCustomAttribute<SomeAttribute>();
}
このような処理を大量に行う場合は、属性の取得結果を一度だけ取得してキャッシュするなどの工夫をした方がよいです。
9-6. 属性を使うべきケース・使わない方がよいケース
属性を使うべきケースは、コード要素に対して補足情報や設定を付けたい場合です。
たとえば、次のような場面では属性が向いています。
| 向いているケース | 例 |
|---|---|
| 入力値のルールを表したい | [Required], [StringLength] |
| フレームワークに設定を伝えたい | [HttpGet], [Table] |
| 古いコードに警告を出したい | [Obsolete] |
| 独自のメタデータを付けたい | [Label("商品名")] |
一方で、複雑な処理そのものを属性で表現しようとすると、コードがわかりにくくなります。
属性はあくまで情報を付ける仕組みです。実際の処理が複雑になる場合は、メソッドやサービスクラスとして明示的に書いた方がよいこともあります。
10. C#属性で初心者がつまずきやすいポイント
10-1. 属性を付けただけでは処理が実行されるとは限らない
C#属性で最も重要なのは、属性を付けただけで必ず処理が実行されるわけではないという点です。
C#[MyCustom]
public class Sample
{
}
このようにカスタム属性を付けても、何かの処理が自動的に実行されるわけではありません。
属性を利用するには、属性を読み取る処理が必要です。標準属性やフレームワークの属性は、コンパイラやフレームワークが読み取って処理してくれる場合があります。
自作の属性では、自分でリフレクションなどを使って取得する処理を書く必要があります。
10-2. Attributeを省略できるためクラス名と見た目が違う
属性クラスは、一般的に〇〇Attributeという名前で定義されます。
C#public class LabelAttribute : Attribute
{
}
しかし、使うときはAttributeを省略できます。
C#[Label]
public class Product
{
}
そのため、初心者は「Labelというクラスはどこにあるの?」と混乱することがあります。
実際には、LabelAttributeクラスが使われています。
属性を探すときは、[Label]と書かれていても、定義名はLabelAttributeかもしれないと覚えておきましょう。
10-3. 属性の対象を間違えるとコンパイルエラーになる
AttributeUsageで属性の適用先が制限されている場合、間違った場所に付けるとコンパイルエラーになります。
C#[AttributeUsage(AttributeTargets.Property)]
public class LabelAttribute : Attribute
{
}
この属性はプロパティにだけ付けられます。
C#public class Product
{
[Label]
public string Name { get; set; }
}
これは正しい使い方です。
しかし、クラスに付けるとエラーになります。
C#[Label]
public class Product
{
}
属性を使うときは、その属性がどの対象に付けられるのかを確認することが大切です。
10-4. プロパティの属性とフィールドの属性は別物
C#では、プロパティとフィールドは別のメンバーです。そのため、プロパティに付けた属性とフィールドに付けた属性は別物です。
C#public class User
{
[Required]
public string Name { get; set; }
}
この場合、Required属性はNameプロパティに付いています。
一方、次のようなコードでは、属性はフィールドに付いています。
C#public class User
{
[Required]
private string name;
}
リフレクションで取得するときも、プロパティの属性を取得するならGetProperties()、フィールドの属性を取得するならGetFields()を使います。
プロパティとフィールドを混同すると、「属性を付けたのに取得できない」という問題が起きやすくなります。
10-5. カスタム属性は取得する処理とセットで考える
カスタム属性は、作って付けるだけでは十分ではありません。
C#[Label("商品名")]
public string Name { get; set; }
この属性を使って表示名を出したいなら、リフレクションでLabelAttributeを取得する処理が必要です。
C#var property = typeof(Product).GetProperty("Name");
var label = property.GetCustomAttribute<LabelAttribute>();
Console.WriteLine(label.Text);
カスタム属性を設計するときは、「誰が、いつ、どのようにこの属性を読み取るのか」まで考えることが重要です。
10-6. 属性に複雑なロジックを持たせすぎない
属性クラスには、プロパティやメソッドを書くこともできます。
しかし、属性に複雑なロジックを持たせすぎるのはおすすめできません。
属性は本来、メタデータを表すためのものです。複雑な処理は、サービスクラスや通常のメソッドとして分けた方がコードの見通しがよくなります。
属性には「設定値」「分類」「表示名」「制限値」などの情報を持たせ、実際の処理は別のクラスに任せる設計が扱いやすいです。
11. C#属性に関するよくある質問
11-1. C#の属性とアノテーションは同じもの?
C#の属性は、Javaなどで使われるアノテーションに似た仕組みです。
どちらも、クラスやメソッドなどにメタデータを付けるために使われます。
ただし、言語ごとに書き方や仕組みは異なります。C#ではAttribute、JavaではAnnotationという名前で扱われます。
C#では次のように角括弧で書きます。
C#[Obsolete]
public void OldMethod()
{
}
Javaでは、一般的に@を使って書きます。
Java@Override
public String toString() {
return "";
}
考え方は似ていますが、C#では「属性」「Attribute」と呼ぶのが正確です。
11-2. 属性は実行時に必ず取得できる?
すべての属性が常に実行時に取得できるとは限りません。
多くの通常の属性はリフレクションで取得できますが、属性の種類や設計によっては、コンパイル時だけ意味を持つものもあります。
たとえば、Conditional属性や一部のコンパイラ向け属性は、実行時に利用するというより、コンパイル時の動作に関係します。
自分でカスタム属性を作る場合は、通常は実行時にリフレクションで取得できます。
11-3. 属性はprivateメンバーにも付けられる?
属性は、privateメンバーにも付けることができます。
C#public class Sample
{
[Obsolete]
private void OldPrivateMethod()
{
}
}
ただし、その属性をどこに付けられるかは、属性側のAttributeUsageによって決まります。
また、privateメンバーの属性をリフレクションで取得する場合は、BindingFlagsを使って非公開メンバーも取得対象に含める必要があります。
C#var method = typeof(Sample).GetMethod(
"OldPrivateMethod",
BindingFlags.NonPublic | BindingFlags.Instance
);
privateメンバーに属性を付けること自体は可能ですが、取得方法には注意が必要です。
11-4. 属性の順番に意味はある?
基本的には、属性の順番に意味を持たせるべきではありません。
C#[Required]
[StringLength(50)]
public string Name { get; set; }
次のように書いても、多くの場合は同じ意味です。
C#[StringLength(50)]
[Required]
public string Name { get; set; }
ただし、フレームワークや自作処理が属性の順番を特別に扱うように実装されている場合は、順番が影響する可能性もあります。
とはいえ、属性の順序に依存する設計はわかりにくくなりやすいため、できるだけ避けるのがよいです。
11-5. 属性は継承される?
属性が継承されるかどうかは、属性のAttributeUsageにあるInherited設定や、取得方法によって変わります。
C#[AttributeUsage(AttributeTargets.Class, Inherited = true)]
public class CategoryAttribute : Attribute
{
}
Inherited = trueであれば、派生クラスから基底クラスの属性を取得できる場合があります。
ただし、すべての属性が自動的に継承されるわけではありません。属性を継承前提で使う場合は、AttributeUsageとリフレクションの取得方法を確認しましょう。
11-6. 初心者はどの属性から覚えるべき?
初心者は、まず次の属性から覚えるとよいです。
| 属性 | 用途 |
|---|---|
Obsolete | 古いメソッドやクラスに警告を出す |
Required | 必須項目を表す |
StringLength | 文字数制限を表す |
Range | 数値の範囲を表す |
Serializable | シリアライズ可能であることを示す |
AttributeUsage | カスタム属性の適用先を指定する |
特にObsoleteは、C#属性の動作を体験しやすいので最初に試すのに向いています。
その後、ASP.NET CoreやEntity Frameworkを学ぶ場合は、HttpGet、Route、Key、Tableなどの属性にも触れていくと理解が深まります。
まとめ
C#属性は、クラス、メソッド、プロパティなどに追加情報を付けるための仕組みです。英語ではAttributeと呼ばれ、コードにメタデータを付与する役割を持ちます。
属性は、角括弧[]を使って記述します。
C#[Obsolete]
public void OldMethod()
{
}
プロパティはオブジェクトの値を扱うメンバーですが、属性はコード要素に付ける補足情報です。この2つは名前が似ていますが、役割はまったく異なります。
C#では、Obsolete、Serializable、Conditional、Required、StringLengthなど、さまざまな標準属性が用意されています。また、ASP.NET CoreやEntity Frameworkなどのフレームワークでも属性は頻繁に使われます。
さらに、Attributeクラスを継承すれば、独自のカスタム属性を作ることもできます。
C#public class LabelAttribute : Attribute
{
public string Text { get; }
public LabelAttribute(string text)
{
Text = text;
}
}
作成したカスタム属性は、リフレクションを使って取得できます。
C#var label = property.GetCustomAttribute<LabelAttribute>();
ただし、属性を付けただけで必ず処理が実行されるわけではありません。属性はあくまでメタデータであり、その情報を読み取って使う仕組みが必要です。
C#属性を理解すると、フレームワークの設定、バリデーション、メタデータ管理、共通処理の設計などがよりわかりやすくなります。初心者はまず、ObsoleteやRequiredなどの身近な属性から試し、慣れてきたらカスタム属性やリフレクションにも挑戦してみるとよいでしょう。

