C# Attributesとは?基本の使い方から自作属性・Reflectionでの取得までわかりやすく解説
はじめに
C# Attributesは、クラス、メソッド、プロパティ、引数などに追加情報を付与するための仕組みです。C#を学び始めたばかりの段階では、[Obsolete]や[Serializable]のような角括弧で囲まれた記述を見て、「これは何をしているのか」と疑問に感じるかもしれません。
Attributesは、コードそのものの動作を直接書き換えるものではありません。対象に対して「このクラスはシリアライズ可能」「このメソッドは非推奨」「このプロパティは必須入力」といったメタデータを付与し、コンパイラ、フレームワーク、またはReflectionを使った処理から参照できるようにする仕組みです。
この記事では、C# Attributesの基本概念から標準Attributesの使い方、自作Attributeの作成方法、Reflectionによる取得方法まで、初心者にもわかりやすく解説します。
1. C# Attributesとは?まず押さえたい基本概念
1-1. Attributeはクラスやメソッドに付与する「メタデータ」
C# Attributesとは、プログラム内の要素に追加情報を付与するための機能です。ここでいう追加情報は「メタデータ」と呼ばれます。
たとえば、次のようなコードがあります。
C#[Obsolete("このメソッドは非推奨です。NewMethodを使用してください。")]
public void OldMethod()
{
Console.WriteLine("古い処理");
}
[Obsolete]がAttributeです。このAttributeは、OldMethodが非推奨であることを示します。
このメソッドを呼び出すと、コンパイラが警告を表示してくれます。
C#OldMethod(); // 警告が表示される
つまりAttributeは、コードに対して「意味」や「補足情報」を与えるための仕組みです。
1-2. コメント・修飾子・アノテーションとの違い
Attributeはコメントとは異なります。
コメントは人間が読むための説明です。
C#// このメソッドは非推奨です
public void OldMethod()
{
}
一方、Attributeはコンパイラやプログラムから読み取れる情報です。
C#[Obsolete]
public void OldMethod()
{
}
また、public、private、staticのような修飾子とも役割が違います。修飾子はC#言語の構文としてアクセス範囲や動作を制御しますが、Attributeは追加情報を付与するためのものです。
Javaなどの言語では「アノテーション」と呼ばれる似た仕組みがあります。C#ではそれに相当するものがAttributesです。
1-3. Attributesが使われる代表的な場面
C# Attributesは、さまざまな場面で使われます。
たとえば、次のような用途があります。
C#[Obsolete]
public void OldMethod()
{
}
非推奨メソッドを示す。
C#[Serializable]
public class User
{
public string Name { get; set; }
}
シリアライズ可能なクラスであることを示す。
C#[Required]
public string Name { get; set; }
入力必須のプロパティであることを示す。
C#[HttpGet]
public IActionResult Index()
{
return View();
}
ASP.NET CoreでHTTP GETリクエストに対応するアクションであることを示す。
このようにAttributesは、C#本体だけでなく、ASP.NET Core、Entity Framework、テストフレームワーク、シリアライズライブラリなどでも広く使われています。
1-4. C# Attributesを理解すると何ができるようになるか
C# Attributesを理解すると、次のようなことができるようになります。
コードに意味のあるメタデータを付与できるようになります。フレームワークがAttributeをどのように利用しているか理解しやすくなります。自作Attributeを作って、独自のルールや設定をコードに埋め込めるようになります。Reflectionを使ってAttributeを取得し、処理を動的に切り替えられるようになります。
特にASP.NET CoreやEntity Frameworkを使う場合、Attributesの理解はとても重要です。ルーティング、バリデーション、データベースマッピングなど、多くの機能がAttributesを活用しています。
2. C# Attributesの基本的な使い方
2-1. Attributeを付与する基本構文
Attributeは、対象の直前に角括弧[]を使って記述します。
C#[AttributeName]
public class SampleClass
{
}
複数行で書くこともできます。
C#[Serializable]
public class User
{
}
値を渡す場合は、通常のメソッド呼び出しのように丸括弧を使います。
C#[Obsolete("このメソッドは非推奨です")]
public void OldMethod()
{
}
基本形は次の通りです。
C#[Attribute名]
対象
または、
C#[Attribute名(引数)]
対象
です。
2-2. クラス・メソッド・プロパティ・引数への付け方
Attributeはさまざまな対象に付与できます。
クラスに付ける例です。
C#[Serializable]
public class User
{
public string Name { get; set; }
}
メソッドに付ける例です。
C#[Obsolete("Use NewMethod instead.")]
public void OldMethod()
{
}
プロパティに付ける例です。
C#public class User
{
[Required]
public string Name { get; set; }
}
引数に付ける例です。
C#public void Register([Required] string userName)
{
}
戻り値にAttributeを付けることもできます。
C#[return: NotNull]
public string GetName()
{
return "Alice";
}
このように、Attributeはクラスだけでなく、メソッド、プロパティ、フィールド、引数、戻り値、アセンブリなどにも付与できます。
2-3. 複数のAttributesを同時に指定する方法
複数のAttributesを指定する場合、次のように1つずつ書けます。
C#[Serializable]
[Obsolete("このクラスは古い形式です")]
public class OldUser
{
}
また、カンマ区切りでまとめて書くこともできます。
C#[Serializable, Obsolete("このクラスは古い形式です")]
public class OldUser
{
}
どちらの書き方でも意味は同じです。ただし、実務では1行が長くなる場合は、1つずつ分けて書いたほうが読みやすくなります。
C#[Serializable]
[Obsolete("Use NewUser instead.")]
public class OldUser
{
}
2-4. Attribute名の「Attribute」サフィックスを省略できる理由
C#のAttributeクラスは、通常Attributeというサフィックスを付けて定義します。
たとえば、ObsoleteAttributeというクラスがあります。
しかし、使用するときは次のように書けます。
C#[Obsolete]
public void OldMethod()
{
}
実際のクラス名はObsoleteAttributeですが、Attributeとして指定するときは末尾のAttributeを省略できます。
つまり、次の2つは同じ意味です。
C#[Obsolete]
public void OldMethod()
{
}
C#[ObsoleteAttribute]
public void OldMethod()
{
}
C#コンパイラがAttributeを解決するときに、Attributeサフィックス付きの名前も候補として扱ってくれるためです。
自作Attributeでも同じです。
C#public class DisplayNameAttribute : Attribute
{
}
この場合、使用時は次のように書けます。
C#[DisplayName]
public class Product
{
}
2-5. Attributeに値を渡す方法
Attributeには値を渡せます。値の渡し方には、主にコンストラクタ引数とプロパティ指定があります。
コンストラクタ引数の例です。
C#[Obsolete("このメソッドは非推奨です")]
public void OldMethod()
{
}
この場合、"このメソッドは非推奨です"という文字列をAttributeに渡しています。
名前付きプロパティを指定する例です。
C#[Sample(1, Name = "Test", IsEnabled = true)]
public class MyClass
{
}
この場合、1はコンストラクタ引数、NameとIsEnabledはプロパティへの指定です。
自作Attributeでは、次のように定義できます。
C#public class SampleAttribute : Attribute
{
public int Id { get; }
public string Name { get; set; }
public bool IsEnabled { get; set; }
public SampleAttribute(int id)
{
Id = id;
}
}
使用例は次の通りです。
C#[Sample(100, Name = "商品", IsEnabled = true)]
public class Product
{
}
必須の値はコンストラクタ引数、任意の値はプロパティで受け取る設計にするとわかりやすくなります。
3. よく使われる標準Attributesの具体例
3-1. [Obsolete]:非推奨メンバーを示す
[Obsolete]は、クラス、メソッド、プロパティなどが非推奨であることを示すAttributeです。
C#public class Sample
{
[Obsolete("OldMethodは非推奨です。NewMethodを使用してください。")]
public void OldMethod()
{
Console.WriteLine("Old");
}
public void NewMethod()
{
Console.WriteLine("New");
}
}
OldMethodを呼び出すと、コンパイル時に警告が表示されます。
C#var sample = new Sample();
sample.OldMethod();
さらに、第二引数にtrueを指定すると、警告ではなくコンパイルエラーにできます。
C#[Obsolete("このメソッドは使用できません。", true)]
public void RemovedMethod()
{
}
APIの移行を促したいときや、古い処理を段階的に廃止したいときに便利です。
3-2. [Serializable]:シリアライズ対象を示す
[Serializable]は、そのクラスがシリアライズ可能であることを示すAttributeです。
C#[Serializable]
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
シリアライズとは、オブジェクトを保存や送受信しやすい形式に変換することです。
たとえば、オブジェクトをファイルに保存したり、ネットワークで送ったりするときに使われます。
ただし、現代の.NETではJSONシリアライズにSystem.Text.JsonやNewtonsoft.Jsonを使うことが多く、必ずしも[Serializable]が必要とは限りません。使用するライブラリや方式によって必要性が異なる点に注意しましょう。
3-3. [Flags]:enumをビットフラグとして扱う
[Flags]は、enumをビットフラグとして扱うことを示すAttributeです。
C#[Flags]
public enum Permission
{
None = 0,
Read = 1,
Write = 2,
Execute = 4
}
このように定義すると、複数の値を組み合わせて使いやすくなります。
C#Permission permission = Permission.Read | Permission.Write;
Console.WriteLine(permission); // Read, Write
ビットフラグでは、各値を2の累乗にするのが基本です。
C#Read = 1 // 0001
Write = 2 // 0010
Execute = 4 // 0100
Delete = 8 // 1000
値が重ならないようにすることで、複数の状態を1つの変数で表現できます。
3-4. [Required]などの検証系Attributes
[Required]は、値が必須であることを示すAttributeです。主にデータ検証で使われます。
C#using System.ComponentModel.DataAnnotations;
public class UserViewModel
{
[Required]
public string Name { get; set; }
[EmailAddress]
public string Email { get; set; }
[Range(0, 120)]
public int Age { get; set; }
}
代表的な検証系Attributesには、次のようなものがあります。
C#[Required]
public string Name { get; set; }
[StringLength(50)]
public string Title { get; set; }
[Range(1, 100)]
public int Quantity { get; set; }
[EmailAddress]
public string Email { get; set; }
ASP.NET Core MVCやRazor Pagesでは、これらのAttributeを使ってモデルの入力チェックを行えます。
C#if (!ModelState.IsValid)
{
return View(model);
}
このように、Attributeを使うことで検証ルールをモデルに近い場所へ記述できます。
3-5. ASP.NET CoreやEntity Frameworkで使われるAttributes
ASP.NET Coreでは、ルーティングやHTTPメソッドの指定にAttributesがよく使われます。
C#[Route("api/users")]
public class UsersController : ControllerBase
{
[HttpGet]
public IActionResult GetAll()
{
return Ok();
}
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
return Ok();
}
[HttpPost]
public IActionResult Create(User user)
{
return Ok();
}
}
Entity Frameworkでは、データベースとの対応関係をAttributeで指定できます。
C#using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
[Table("Users")]
public class User
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
[Column("email_address")]
public string Email { get; set; }
}
このように、C# Attributesはフレームワークに対して設定情報を伝える役割を持ちます。
ただし、Entity FrameworkではFluent APIでも同様の設定ができます。Attributeで書くべきかFluent APIで書くべきかは、プロジェクトの方針に合わせて選ぶとよいでしょう。
4. Attributeの指定対象と制御方法
4-1. Attributeを付けられる主な対象
Attributeはさまざまな要素に付けられます。主な対象には、次のようなものがあります。
C#[assembly: AssemblyTitle("SampleApp")]
アセンブリに付ける例です。
C#[module: SomeModuleAttribute]
モジュールに付ける例です。
C#[Serializable]
public class User
{
}
クラスに付ける例です。
C#[Obsolete]
public void OldMethod()
{
}
メソッドに付ける例です。
C#[Required]
public string Name { get; set; }
プロパティに付ける例です。
C#[NonSerialized]
private string cache;
フィールドに付ける例です。
C#public void SetName([Required] string name)
{
}
引数に付ける例です。
対象を明示したい場合は、次のようにターゲット指定を使います。
C#[property: Required]
public string Name { get; set; }
通常は省略できますが、対象が曖昧になりやすい場面では明示すると意図が伝わりやすくなります。
4-2. AttributeTargetsで対象を制限する
自作Attributeを作るときは、AttributeUsageを使って、どの対象に付けられるAttributeなのかを制限できます。
C#[AttributeUsage(AttributeTargets.Class)]
public class MyClassAttribute : Attribute
{
}
このAttributeはクラスにだけ付けられます。
C#[MyClass]
public class Sample
{
}
メソッドに付けようとするとエラーになります。
C#public class Sample
{
[MyClass] // エラー
public void Method()
{
}
}
複数の対象を許可したい場合は、ビット演算子|で組み合わせます。
C#[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAttribute : Attribute
{
}
この場合、クラスとメソッドの両方に付けられます。
4-3. AllowMultipleで複数指定を許可する
同じAttributeを同じ対象に複数付けたい場合は、AllowMultiple = trueを指定します。
C#[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class TagAttribute : Attribute
{
public string Name { get; }
public TagAttribute(string name)
{
Name = name;
}
}
使用例です。
C#[Tag("重要")]
[Tag("管理画面")]
[Tag("顧客向け")]
public class UserPage
{
}
AllowMultipleを指定しない場合、既定値はfalseです。そのため、同じAttributeを複数付けるとコンパイルエラーになります。
C#[AttributeUsage(AttributeTargets.Class)]
public class CategoryAttribute : Attribute
{
}
C#[Category]
[Category] // エラー
public class Product
{
}
複数指定が必要な設計かどうかを考えたうえで設定しましょう。
4-4. Inheritedで継承時の扱いを制御する
Inheritedは、Attributeが派生クラスやオーバーライドメンバーに継承されるかどうかを制御します。
C#[AttributeUsage(AttributeTargets.Class, Inherited = true)]
public class RoleAttribute : Attribute
{
public string Name { get; }
public RoleAttribute(string name)
{
Name = name;
}
}
使用例です。
C#[Role("Admin")]
public class BaseController
{
}
public class UserController : BaseController
{
}
Inherited = trueの場合、ReflectionでUserControllerを調べると、基底クラスに付けられたRoleAttributeを取得できる場合があります。
一方、Inherited = falseにすると、派生クラスには引き継がれません。
C#[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class NoInheritAttribute : Attribute
{
}
継承先にも意味を持たせたいAttributeなのか、そのクラスだけに限定したいAttributeなのかを考えて設定することが大切です。
4-5. AttributeUsageの基本構文と実例
AttributeUsageは、自作Attributeの利用ルールを指定するためのAttributeです。
基本構文は次の通りです。
C#[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Method,
AllowMultiple = false,
Inherited = true)]
public class SampleAttribute : Attribute
{
}
実例を見てみましょう。
C#[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Method,
AllowMultiple = true,
Inherited = false)]
public class DescriptionAttribute : Attribute
{
public string Text { get; }
public DescriptionAttribute(string text)
{
Text = text;
}
}
このAttributeは、クラスまたはメソッドに付けられます。同じ対象に複数付けられます。継承先には引き継がれません。
使用例です。
C#[Description("ユーザー管理機能")]
[Description("管理者向け")]
public class UserController
{
[Description("ユーザー一覧を取得します")]
public void GetUsers()
{
}
}
AttributeUsageを適切に設定することで、自作Attributeの誤用を防ぎ、コードの意図を明確にできます。
5. 自作Attributeの作り方
5-1. Attributeクラスを継承して独自属性を作る
自作Attributeを作るには、System.Attributeクラスを継承します。
C#public class MyAttribute : Attribute
{
}
一般的には、クラス名の末尾にAttributeを付けます。
C#public class DisplayNameAttribute : Attribute
{
}
使用時は、Attributeを省略して次のように書けます。
C#[DisplayName]
public class Product
{
}
自作Attributeを作ると、独自のメタデータをクラスやメソッドに付与できます。
5-2. 自作Attributeの基本コード例
簡単な自作Attributeの例を見てみましょう。
C#using System;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MemoAttribute : Attribute
{
public string Text { get; }
public MemoAttribute(string text)
{
Text = text;
}
}
このAttributeは、クラスまたはメソッドにメモを付けるためのものです。
使用例です。
C#[Memo("商品を表すクラスです")]
public class Product
{
[Memo("税込価格を計算します")]
public decimal GetPriceWithTax(decimal price)
{
return price * 1.1m;
}
}
この時点では、Attributeを付けただけです。MemoAttributeの情報を使って何か処理をしたい場合は、Reflectionなどを使って読み取る必要があります。
5-3. コンストラクタ引数で必須値を受け取る
Attributeに必須の値を持たせたい場合は、コンストラクタ引数を使います。
C#[AttributeUsage(AttributeTargets.Class)]
public class TableNameAttribute : Attribute
{
public string Name { get; }
public TableNameAttribute(string name)
{
Name = name;
}
}
使用例です。
C#[TableName("users")]
public class User
{
}
この場合、TableNameAttributeを使うには必ずテーブル名を指定する必要があります。
C#[TableName] // エラー
public class Product
{
}
必須の情報はコンストラクタで受け取ると、Attributeの指定漏れを防ぎやすくなります。
5-4. プロパティで任意の値を受け取る
任意の値を受け取りたい場合は、set可能なプロパティを用意します。
C#[AttributeUsage(AttributeTargets.Class)]
public class ApiInfoAttribute : Attribute
{
public string Name { get; }
public string Version { get; set; }
public bool IsPublic { get; set; }
public ApiInfoAttribute(string name)
{
Name = name;
}
}
使用例です。
C#[ApiInfo("User API", Version = "v1", IsPublic = true)]
public class UserApi
{
}
Nameは必須、VersionとIsPublicは任意です。
このように、必須値はコンストラクタ引数、任意値はプロパティで表現すると、使いやすいAttributeになります。
5-5. 自作Attributeをクラスやメソッドに付与する
自作Attributeは、標準Attributeと同じように使えます。
C#[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuditLogAttribute : Attribute
{
public string ActionName { get; }
public AuditLogAttribute(string actionName)
{
ActionName = actionName;
}
}
クラスに付与する例です。
C#[AuditLog("ユーザー管理")]
public class UserService
{
}
メソッドに付与する例です。
C#public class UserService
{
[AuditLog("ユーザー作成")]
public void CreateUser()
{
}
[AuditLog("ユーザー削除")]
public void DeleteUser()
{
}
}
このように、処理の種類や分類などをAttributeとして定義しておくと、後からReflectionで読み取り、ログ出力や権限チェックに利用できます。
5-6. 自作Attributeを使うべきケースと避けるべきケース
自作Attributeは便利ですが、何でもAttributeにすればよいわけではありません。
使うべきケースは、メタデータとして表現したい情報がある場合です。たとえば、表示名、カテゴリ、権限、入力ルール、ログ対象、マッピング情報などです。
C#[DisplayName("商品名")]
public string Name { get; set; }
このような情報は、コードの対象に紐づいた補足情報なのでAttributeに向いています。
一方で、複雑な処理そのものをAttributeに入れるのは避けるべきです。
C#public class BadAttribute : Attribute
{
public void ExecuteComplexProcess()
{
// 複雑な業務処理
}
}
Attributeは基本的にメタデータを表すものです。複雑な業務ロジックはサービスクラスなどに分離し、Attributeには識別情報や設定値だけを持たせる設計が望ましいです。
6. ReflectionでAttributeを取得する方法
6-1. Reflectionとは何か
Reflectionとは、実行時に型情報を調べたり、メソッドやプロパティを取得したりできる仕組みです。
たとえば、あるクラスの名前、プロパティ一覧、メソッド一覧、付与されているAttributeなどを実行時に調べられます。
C#Type type = typeof(User);
Console.WriteLine(type.Name);
Attributeはメタデータとして保存されるため、Reflectionを使って取得できます。
C#var attributes = type.GetCustomAttributes(false);
Reflectionを使うことで、Attributeの値を読み取り、それに応じて処理を切り替えられます。
6-2. GetCustomAttributesでAttribute一覧を取得する
GetCustomAttributesを使うと、対象に付与されたAttributeの一覧を取得できます。
C#using System;
[Obsolete("古いクラスです")]
public class OldClass
{
}
public class Program
{
public static void Main()
{
Type type = typeof(OldClass);
object[] attributes = type.GetCustomAttributes(false);
foreach (object attribute in attributes)
{
Console.WriteLine(attribute.GetType().Name);
}
}
}
結果として、ObsoleteAttributeなどのAttribute情報を取得できます。
特定の型のAttributeだけを取得したい場合は、次のように書けます。
C#var attributes = type.GetCustomAttributes(typeof(ObsoleteAttribute), false);
ジェネリック版を使うと、より扱いやすくなります。
C#var attributes = type.GetCustomAttributes<ObsoleteAttribute>();
6-3. GetCustomAttributeで特定のAttributeを取得する
1つのAttributeを取得したい場合は、GetCustomAttributeを使います。
C#using System;
using System.Reflection;
[Obsolete("このクラスは非推奨です")]
public class OldClass
{
}
public class Program
{
public static void Main()
{
Type type = typeof(OldClass);
ObsoleteAttribute? attribute =
type.GetCustomAttribute<ObsoleteAttribute>();
if (attribute != null)
{
Console.WriteLine(attribute.Message);
}
}
}
GetCustomAttribute<T>()は、指定したAttributeが存在しない場合にnullを返します。そのため、取得後はnullチェックを行うのが基本です。
C#if (attribute != null)
{
Console.WriteLine(attribute.Message);
}
6-4. クラスに付与されたAttributeを取得する
自作Attributeをクラスに付与し、Reflectionで取得する例を見てみましょう。
C#using System;
using System.Reflection;
[AttributeUsage(AttributeTargets.Class)]
public class DisplayNameAttribute : Attribute
{
public string Name { get; }
public DisplayNameAttribute(string name)
{
Name = name;
}
}
[DisplayName("商品")]
public class Product
{
}
取得処理です。
C#Type type = typeof(Product);
DisplayNameAttribute? attribute =
type.GetCustomAttribute<DisplayNameAttribute>();
if (attribute != null)
{
Console.WriteLine(attribute.Name);
}
出力結果は次の通りです。
C#商品
このように、クラスに付与したAttributeの値を実行時に読み取れます。
6-5. メソッド・プロパティに付与されたAttributeを取得する
メソッドに付与されたAttributeを取得するには、GetMethodでメソッド情報を取得してからAttributeを読み取ります。
C#using System;
using System.Reflection;
[AttributeUsage(AttributeTargets.Method)]
public class CommandAttribute : Attribute
{
public string Name { get; }
public CommandAttribute(string name)
{
Name = name;
}
}
public class UserCommand
{
[Command("create-user")]
public void CreateUser()
{
}
}
取得処理です。
C#MethodInfo? method = typeof(UserCommand).GetMethod("CreateUser");
CommandAttribute? attribute =
method?.GetCustomAttribute<CommandAttribute>();
if (attribute != null)
{
Console.WriteLine(attribute.Name);
}
プロパティに付与されたAttributeを取得する例です。
C#[AttributeUsage(AttributeTargets.Property)]
public class LabelAttribute : Attribute
{
public string Text { get; }
public LabelAttribute(string text)
{
Text = text;
}
}
public class User
{
[Label("ユーザー名")]
public string Name { get; set; }
}
取得処理です。
C#PropertyInfo? property = typeof(User).GetProperty("Name");
LabelAttribute? attribute =
property?.GetCustomAttribute<LabelAttribute>();
if (attribute != null)
{
Console.WriteLine(attribute.Text);
}
複数のプロパティをまとめて調べることもできます。
C#foreach (PropertyInfo property in typeof(User).GetProperties())
{
LabelAttribute? label =
property.GetCustomAttribute<LabelAttribute>();
if (label != null)
{
Console.WriteLine($"{property.Name}: {label.Text}");
}
}
6-6. Attributeの値を読み取って処理を分岐する実例
Attributeの値を使って処理を分岐する例を見てみましょう。
C#[AttributeUsage(AttributeTargets.Method)]
public class RequiresRoleAttribute : Attribute
{
public string Role { get; }
public RequiresRoleAttribute(string role)
{
Role = role;
}
}
public class AdminService
{
[RequiresRole("Admin")]
public void DeleteUser()
{
Console.WriteLine("ユーザーを削除しました");
}
}
実行前に権限をチェックする処理です。
C#using System;
using System.Reflection;
public class Program
{
public static void Main()
{
string currentUserRole = "User";
MethodInfo? method = typeof(AdminService).GetMethod("DeleteUser");
RequiresRoleAttribute? attribute =
method?.GetCustomAttribute<RequiresRoleAttribute>();
if (attribute != null && attribute.Role != currentUserRole)
{
Console.WriteLine("権限がありません");
return;
}
var service = new AdminService();
service.DeleteUser();
}
}
この例では、DeleteUserメソッドに必要な権限をAttributeで定義し、Reflectionで読み取っています。
ただし、これはあくまで仕組みを理解するための簡単な例です。実務で認可処理を実装する場合は、ASP.NET Coreの認証・認可機能など、フレームワークが提供する仕組みを使うのが基本です。
7. 自作AttributeとReflectionを組み合わせた実践例
7-1. 表示名をAttributeで定義する例
プロパティの表示名をAttributeで定義する例です。
C#[AttributeUsage(AttributeTargets.Property)]
public class DisplayLabelAttribute : Attribute
{
public string Label { get; }
public DisplayLabelAttribute(string label)
{
Label = label;
}
}
モデルクラスに付与します。
C#public class Product
{
[DisplayLabel("商品ID")]
public int Id { get; set; }
[DisplayLabel("商品名")]
public string Name { get; set; }
[DisplayLabel("価格")]
public decimal Price { get; set; }
}
Reflectionで表示名を取得します。
C#foreach (PropertyInfo property in typeof(Product).GetProperties())
{
DisplayLabelAttribute? label =
property.GetCustomAttribute<DisplayLabelAttribute>();
string displayName = label?.Label ?? property.Name;
Console.WriteLine($"{property.Name} => {displayName}");
}
出力例です。
C#Id => 商品ID
Name => 商品名
Price => 価格
このような仕組みは、画面表示、CSV出力、ログ出力などで利用できます。
7-2. 権限チェックをAttributeで管理する例
メソッドごとに必要な権限をAttributeで定義する例です。
C#[AttributeUsage(AttributeTargets.Method)]
public class PermissionAttribute : Attribute
{
public string Name { get; }
public PermissionAttribute(string name)
{
Name = name;
}
}
サービスクラスに付与します。
C#public class OrderService
{
[Permission("Order.Read")]
public void GetOrders()
{
Console.WriteLine("注文一覧を取得します");
}
[Permission("Order.Delete")]
public void DeleteOrder()
{
Console.WriteLine("注文を削除します");
}
}
実行時に権限を確認します。
C#public static bool HasPermission(string userPermission, MethodInfo method)
{
PermissionAttribute? attribute =
method.GetCustomAttribute<PermissionAttribute>();
if (attribute == null)
{
return true;
}
return attribute.Name == userPermission;
}
使用例です。
C#MethodInfo? method = typeof(OrderService).GetMethod("DeleteOrder");
if (method != null && HasPermission("Order.Read", method))
{
var service = new OrderService();
service.DeleteOrder();
}
else
{
Console.WriteLine("権限がありません");
}
Attributeを使うと、権限情報をメソッドの近くに記述できます。ただし、実務ではセキュリティ要件が複雑になるため、独自実装だけに頼らず、フレームワークの認可機能と組み合わせることが重要です。
7-3. 入力チェックルールをAttributeで定義する例
入力チェックルールもAttributeで定義できます。
C#[AttributeUsage(AttributeTargets.Property)]
public class RequiredTextAttribute : Attribute
{
public string ErrorMessage { get; set; } = "必須項目です";
}
モデルに付与します。
C#public class UserInput
{
[RequiredText(ErrorMessage = "名前を入力してください")]
public string Name { get; set; }
[RequiredText(ErrorMessage = "メールアドレスを入力してください")]
public string Email { get; set; }
}
Reflectionでチェックします。
C#public static List<string> Validate(object target)
{
var errors = new List<string>();
Type type = target.GetType();
foreach (PropertyInfo property in type.GetProperties())
{
RequiredTextAttribute? attribute =
property.GetCustomAttribute<RequiredTextAttribute>();
if (attribute == null)
{
continue;
}
object? value = property.GetValue(target);
if (value is null || string.IsNullOrWhiteSpace(value.ToString()))
{
errors.Add(attribute.ErrorMessage);
}
}
return errors;
}
使用例です。
C#var input = new UserInput
{
Name = "",
Email = ""
};
List<string> errors = Validate(input);
foreach (string error in errors)
{
Console.WriteLine(error);
}
出力例です。
C#名前を入力してください
メールアドレスを入力してください
このような仕組みは学習用としてわかりやすいですが、実務ではSystem.ComponentModel.DataAnnotationsの[Required]など、標準の検証Attributeを先に検討しましょう。
7-4. Attributeを使って処理を共通化する考え方
Attributeを使う大きなメリットは、対象ごとの設定情報をコードの近くに置きながら、処理そのものは共通化できることです。
たとえば、CSV出力を考えてみます。
C#[AttributeUsage(AttributeTargets.Property)]
public class CsvColumnAttribute : Attribute
{
public string Header { get; }
public int Order { get; set; }
public CsvColumnAttribute(string header)
{
Header = header;
}
}
モデルに設定します。
C#public class Product
{
[CsvColumn("商品ID", Order = 1)]
public int Id { get; set; }
[CsvColumn("商品名", Order = 2)]
public string Name { get; set; }
[CsvColumn("価格", Order = 3)]
public decimal Price { get; set; }
}
CSV出力側は、Attributeを見てヘッダーや出力順を決めます。
C#var columns = typeof(Product)
.GetProperties()
.Select(p => new
{
Property = p,
Attribute = p.GetCustomAttribute<CsvColumnAttribute>()
})
.Where(x => x.Attribute != null)
.OrderBy(x => x.Attribute!.Order);
foreach (var column in columns)
{
Console.WriteLine(column.Attribute!.Header);
}
このように、個別のクラスには設定だけを書き、実際の処理は共通ロジックにまとめることができます。
7-5. 実務で使いやすい設計パターン
実務で自作Attributeを使う場合は、次のような設計にすると扱いやすくなります。
まず、Attributeには設定値だけを持たせます。
C#[AttributeUsage(AttributeTargets.Method)]
public class RetryAttribute : Attribute
{
public int Count { get; }
public RetryAttribute(int count)
{
Count = count;
}
}
次に、Attributeを読み取る処理を別クラスに分離します。
C#public class RetryPolicyResolver
{
public int GetRetryCount(MethodInfo method)
{
RetryAttribute? attribute =
method.GetCustomAttribute<RetryAttribute>();
return attribute?.Count ?? 0;
}
}
メソッドには設定だけを書きます。
C#public class ExternalApiService
{
[Retry(3)]
public void CallApi()
{
Console.WriteLine("APIを呼び出します");
}
}
この設計では、Attribute、読み取り処理、実際の業務処理が分離されます。
Attributeに処理を詰め込まず、Attributeは「宣言」、別クラスは「解釈」、サービスは「業務処理」と役割を分けるのがポイントです。
8. C# Attributesを使うときの注意点
8-1. Attributeだけでは処理は実行されない
Attributeを付けただけでは、基本的に処理は実行されません。
C#[MyLog]
public void Save()
{
Console.WriteLine("保存しました");
}
このように書いても、[MyLog]が自動的にログを出力してくれるわけではありません。
Attributeはあくまでメタデータです。その情報を使って処理をしたい場合は、コンパイラ、フレームワーク、Reflectionを使った自作処理など、Attributeを読み取る側が必要です。
ASP.NET Coreの[HttpGet]や[Authorize]が機能するのは、ASP.NET CoreフレームワークがそれらのAttributeを読み取って処理しているためです。
自作Attributeを作る場合も、読み取り処理を忘れないようにしましょう。
8-2. Reflectionの使いすぎによるパフォーマンス低下
Reflectionは便利ですが、通常のメソッド呼び出しやプロパティアクセスに比べるとコストが高くなる場合があります。
たとえば、毎回大量のオブジェクトに対してReflectionでAttributeを取得すると、パフォーマンスに影響する可能性があります。
C#foreach (var item in items)
{
var attributes = item.GetType().GetCustomAttributes();
}
このような処理を頻繁に実行する場合は、取得結果をキャッシュすることを検討しましょう。
C#private static readonly Dictionary<Type, object[]> AttributeCache = new();
public static object[] GetAttributes(Type type)
{
if (!AttributeCache.TryGetValue(type, out var attributes))
{
attributes = type.GetCustomAttributes(false);
AttributeCache[type] = attributes;
}
return attributes;
}
Reflectionを使う処理は、アプリケーション起動時や初回アクセス時にまとめて行い、以降はキャッシュを使う設計にすると効率的です。
8-3. Attributeに複雑なロジックを持たせない
Attributeクラスには、複雑な業務ロジックを持たせないほうがよいです。
避けたい例です。
C#public class CalculatePriceAttribute : Attribute
{
public decimal Calculate(decimal price)
{
// 複雑な割引計算
return price * 0.9m;
}
}
このようにすると、Attributeが単なるメタデータではなく、業務処理の実行主体になってしまいます。
望ましい設計は、Attributeには設定値だけを持たせることです。
C#public class DiscountAttribute : Attribute
{
public decimal Rate { get; }
public DiscountAttribute(decimal rate)
{
Rate = rate;
}
}
実際の計算は別クラスで行います。
C#public class DiscountCalculator
{
public decimal Calculate(decimal price, decimal rate)
{
return price * (1 - rate);
}
}
Attributeの責務を小さく保つことで、テストしやすく、保守しやすいコードになります。
8-4. 可読性を下げないための命名ルール
自作Attributeを作るときは、名前から用途がわかるようにしましょう。
よい例です。
C#public class DisplayNameAttribute : Attribute
{
}
C#public class RequiresPermissionAttribute : Attribute
{
}
C#public class CsvColumnAttribute : Attribute
{
}
避けたい例です。
C#public class DataAttribute : Attribute
{
}
C#public class InfoAttribute : Attribute
{
}
C#public class CustomAttribute : Attribute
{
}
名前が抽象的すぎると、何のためのAttributeなのかがわかりにくくなります。
また、使用時はAttributeサフィックスが省略されることを意識しましょう。
C#[RequiresPermission("Admin")]
public void DeleteUser()
{
}
このように読んだときに自然な名前にすると、コードの可読性が上がります。
8-5. 標準Attributesで代替できないか確認する
自作Attributeを作る前に、標準Attributesやフレームワークが用意しているAttributeで代替できないか確認しましょう。
たとえば、入力必須を表すために次のような自作Attributeを作る前に、
C#public class MyRequiredAttribute : Attribute
{
}
標準の[Required]を使えないか検討します。
C#[Required]
public string Name { get; set; }
表示名を表す場合も、既存の[Display]が使えることがあります。
C#[Display(Name = "商品名")]
public string Name { get; set; }
標準Attributesを使うと、ASP.NET Core MVCや各種ライブラリと連携しやすくなります。独自実装が必要な場合だけ、自作Attributeを作るのがおすすめです。
9. C# Attributesに関するよくある疑問
9-1. Attributeとカスタム属性の違いは?
Attributeは、C#における属性機能全般を指します。
カスタム属性は、自分で作成したAttributeのことです。
たとえば、[Obsolete]や[Serializable]は標準で用意されているAttributeです。
C#[Obsolete]
public void OldMethod()
{
}
一方、次のように自分で定義したものはカスタム属性です。
C#public class MyAttribute : Attribute
{
}
C#[My]
public class Sample
{
}
つまり、カスタム属性はAttributeの一種です。
9-2. Attributeは実行時に取得できる?
はい、取得できます。
Reflectionを使うことで、実行時にAttributeを取得できます。
C#var attribute = typeof(Sample)
.GetCustomAttribute<MyAttribute>();
ただし、Attributeの種類や取得方法によっては、継承の扱いや対象の違いに注意が必要です。
たとえば、クラスのAttributeを取得する場合と、メソッドやプロパティのAttributeを取得する場合では、取得元となるReflectionの型が異なります。
C#Type type = typeof(Sample);
MethodInfo? method = type.GetMethod("Execute");
PropertyInfo? property = type.GetProperty("Name");
それぞれに対してGetCustomAttributeやGetCustomAttributesを呼び出します。
9-3. Attributeにメソッドは定義できる?
Attributeクラスにも通常のクラスと同じようにメソッドを定義できます。
C#public class SampleAttribute : Attribute
{
public void Execute()
{
Console.WriteLine("実行");
}
}
ただし、Attributeに複雑な処理を持たせる設計はおすすめできません。
Attributeは基本的にメタデータを表すものです。設定値や識別情報を持たせ、それを読み取る処理は別のクラスに分けるほうが保守しやすくなります。
望ましい例です。
C#public class RetryAttribute : Attribute
{
public int Count { get; }
public RetryAttribute(int count)
{
Count = count;
}
}
このように、Attributeは「何回リトライするか」という設定だけを持ち、実際のリトライ処理は別クラスで行う設計が扱いやすいです。
9-4. Attributeの値に使える型は?
Attributeの引数やプロパティに使える型には制限があります。
代表的には、次のような型が使えます。
C#string
int
bool
double
Type
enum
また、これらの配列も使えます。
C#public class SampleAttribute : Attribute
{
public string Name { get; }
public Type TargetType { get; }
public int[] Numbers { get; set; }
public SampleAttribute(string name, Type targetType)
{
Name = name;
TargetType = targetType;
}
}
使用例です。
C#[Sample("Test", typeof(string), Numbers = new[] { 1, 2, 3 })]
public class MyClass
{
}
一方で、任意のオブジェクトや複雑なクラスのインスタンスをAttribute引数に直接渡すことはできません。
C#// このような複雑なオブジェクトはAttribute引数に向いていません
[Sample(new SomeClass())]
public class MyClass
{
}
Attributeの値は、コンパイル時に決定できる定数的な値にする必要があります。
9-5. Attributeは継承先にも引き継がれる?
Attributeが継承先にも引き継がれるかどうかは、AttributeUsageの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 BaseClass
{
}
public class DerivedClass : BaseClass
{
}
Inherited = trueであれば、Reflectionで派生クラスを調べると、基底クラスのAttributeを取得できる場合があります。
C#var attribute = typeof(DerivedClass)
.GetCustomAttribute<CategoryAttribute>(inherit: true);
ただし、すべての対象で同じように継承されるわけではありません。クラス、メソッド、プロパティなど、対象によって扱いが異なる場合があります。
継承を前提に使う場合は、AttributeUsageの設定とReflectionでの取得方法を確認しておきましょう。
まとめ
C# Attributesは、クラス、メソッド、プロパティ、引数などにメタデータを付与するための仕組みです。[Obsolete]、[Serializable]、[Flags]、[Required]などの標準Attributesは、C#や.NETのさまざまな場面で使われています。
Attributeの基本構文はシンプルです。
C#[AttributeName]
public class Sample
{
}
値を渡す場合は、コンストラクタ引数や名前付きプロパティを使います。
C#[Sample(100, Name = "Test")]
public class MyClass
{
}
自作Attributeを作るには、Attributeクラスを継承します。
C#public class MyAttribute : Attribute
{
}
さらに、AttributeUsageを使うことで、付与できる対象、複数指定の可否、継承時の扱いを制御できます。
C#[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public class MyAttribute : Attribute
{
}
Attributeの情報を実行時に利用したい場合は、Reflectionを使います。
C#var attribute = typeof(Sample)
.GetCustomAttribute<MyAttribute>();
ただし、Attributeは付けただけで処理が実行されるものではありません。Attributeはあくまでメタデータであり、それを読み取って処理する仕組みが必要です。
C# Attributesを正しく理解すると、ASP.NET Core、Entity Framework、入力検証、権限管理、ログ出力、CSV出力など、さまざまな設計をより柔軟にできます。まずは標準Attributesの使い方を押さえ、そのうえで必要に応じて自作AttributeとReflectionを組み合わせて活用していきましょう。

