C#を用いた開発において、プログラムの要素に対してメタデータを付与する「属性(Attribute)」は、フレームワークの制御や実行時の動作変更において欠かせない存在です。

特に大規模なアプリケーションや、ASP.NET Core、Entity Framework Coreなどのモダンなライブラリを利用する場合、一つのクラスやメソッドに対して複数の属性を同時に指定する機会は非常に多くなります。

属性を複数指定する際には、単に並べて書く以外にも、コードの可読性を高めるための書き方や、同じ種類の属性を重複して適用するための特別な設定、さらには実行時にそれらを効率的に取得するためのテクニックが存在します。

本記事では、C#における属性の複数指定に関する基礎知識から、自作属性における「複数許可」の設定、そしてC# 11以降で利用可能な最新の記法まで、プロフェッショナルな視点で詳しく解説します。

C#における属性(Attribute)の基本概念

属性とは、クラス、メソッド、プロパティ、引数などのプログラム要素に対して付加情報を与えるための仕組みです。

これは「宣言的プログラミング」を実現する要素であり、コードそのもののロジックを変更することなく、外部のツールやコンパイラ、あるいは実行時のリフレクション機能に対して特定の指示を出すことができます。

属性とは何か

属性は、システムに対して「このメソッドはWeb APIのエンドポイントである」「このプロパティはデータベースの主キーである」といった意味付けを行います。

C#の属性はすべて System.Attribute クラスを継承したクラスとして定義されており、コンパイル時にメタデータとしてアセンブリ内に格納されます。

複数の属性を指定するシーン

実際の開発現場では、一つの要素に対して複数の役割を持たせることが多々あります。

例えば、ASP.NET Coreのコントローラーメソッドでは、以下のような複数の情報を同時に定義することが一般的です。

  • どのURLパスに反応するか(ルーティング属性)
  • どのHTTPメソッド(GET/POSTなど)を受け付けるか
  • 認証・認可が必要かどうか(Authorize属性)
  • レスポンスの型やステータスコードの定義(ProducesResponseType属性)

これらの情報を整理して記述するためには、C#が提供する属性の複数指定ルールを正確に理解しておく必要があります。

複数属性を指定する2つの基本構文

C#では、一つの対象に対して複数の属性を付与する場合、主に2種類の書き方が認められています。

どちらの方法をとってもプログラムの動作に違いはありませんが、チームの開発規約や可読性の観点から使い分けられます。

独立したブラケットで記述する方法

最も一般的で、視認性に優れているのが、各属性を個別の角括弧 [] で囲んで記述する方法です。

C#
using System;

namespace AttributeExample
{
    // 各属性を独立したブラケットで指定
    [Serializable]
    [Obsolete("このクラスは次期バージョンで廃止予定です。")]
    public class LegacyDataProcessor
    {
        public void Process()
        {
            Console.WriteLine("処理を実行中...");
        }
    }

    class Program
    {
        static void Main()
        {
            var processor = new LegacyDataProcessor();
            processor.Process();
        }
    }
}

この書き方のメリットは、1行に1つの属性を記述することで、属性が増えても縦に綺麗に並ぶ点にあります。

特に属性に多くの引数を渡す場合、この形式の方が圧倒的に読みやすくなります。

カンマ区切りで一括記述する方法

もう一つの方法は、一つの角括弧 [] の中に、複数の属性をカンマ , で区切って記述する方法です。

C#
using System;

namespace AttributeExample
{
    // カンマ区切りで複数の属性を1つのブラケットにまとめる
    [Serializable, Obsolete("新しいクラスへの移行を検討してください。")]
    public class CompactDataProcessor
    {
        public void Process()
        {
            Console.WriteLine("コンパクトな定義での処理...");
        }
    }
}

この記法は、属性名が短く、引数も少ない場合にコードを簡潔に保つのに役立ちます。

しかし、属性が増えてくると1行が長くなりすぎるため、可読性を損なわない範囲で使用するのが適切です。

どちらの記述方法を選ぶべきか

結論から言えば、モダンなC#開発においては「独立したブラケットで記述する方法」が推奨される傾向にあります。

理由は以下の通りです。

  1. 差分管理の容易さ:Gitなどのバージョン管理システムで、どの属性が追加・削除されたかが行単位で明確になります。
  2. コメントの付けやすさ:特定の属性に対してのみコメントを付加したい場合に、行が分かれている方が容易です。
  3. 視認性:属性の数が増えても、左端に [ が並ぶため、属性が適用されていることが一目でわかります。

属性の対象(Attribute Targets)を明示する場合

複数の属性を指定する際、特に「メソッドの戻り値」や「バッキングフィールド」など、曖昧さが発生しやすい箇所では「対象指定子(Target Specifiers)」を併用することがあります。

対象指定子(Target Specifiers)の活用

例えば、プロパティに対して属性を付与する場合、その属性を「プロパティ自体」に適用したいのか、あるいはコンパイラが自動生成する「バッキングフィールド」に適用したいのかを明示する必要があります。

C#
using System;

public class User
{
    // field: を指定することで、自動実装プロパティの裏側にあるフィールドに属性を適用
    [field: NonSerialized]
    [Required] // これはプロパティ自体に適用
    public string Password { get; set; }
}

複数の属性に異なる対象を指定する場合も、以下のように並べて記述可能です。

C#
// 戻り値とメソッド本体にそれぞれ別の属性を指定
[method: LogAttribute]
[return: Description("処理結果のステータス")]
public int Execute() 
{
    return 1;
}

このように、対象指定子を使用する場合でも、前述のカンマ区切りや独立ブラケットのルールは同様に適用されます。

自作属性で同じ属性を複数回適用する方法

標準的な属性(例:[Serializable])は、通常一つの要素に対して一度しか指定できません。

同じ属性を2回以上書くとコンパイルエラーになります。

しかし、自作の属性を作成する際には、「同じ属性を複数回重ねて指定したい」というケースがあります。

これを実現するためには、属性クラスの定義時に AttributeUsage 属性を使用し、AllowMultipleプロパティをtrueに設定する必要があります。

AttributeUsage属性の役割

AttributeUsage は、作成する属性が「どこに適用できるか(クラスか、メソッドかなど)」や「継承されるか」を制御するためのメタ属性です。

AllowMultipleプロパティの設定

デフォルトでは AllowMultiplefalse です。

これを true にすることで、同一要素への重複指定が許可されます。

実装例:複数のタグを付与できる属性

以下のコードは、開発担当者を複数指定できるようにした自作属性の例です。

C#
using System;
using System.Collections.Generic;

namespace CustomAttributeExample
{
    // AllowMultiple = true により、同じ要素に複数回適用可能になる
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
    public class AuthorAttribute : Attribute
    {
        public string Name { get; }
        public string Date { get; }

        public AuthorAttribute(string name, string date)
        {
            Name = name;
            Date = date;
        }
    }

    // 同じ属性を複数回指定
    [Author("田中太郎", "2024-05-01")]
    [Author("佐藤次郎", "2024-06-15")]
    public class ProjectModule
    {
        public void Run() => Console.WriteLine("モジュール実行中");
    }

    class Program
    {
        static void Main()
        {
            var type = typeof(ProjectModule);
            // 属性をすべて取得
            var attributes = type.GetCustomAttributes(typeof(AuthorAttribute), false);

            Console.WriteLine($"{type.Name} の担当者一覧:");
            foreach (AuthorAttribute auth in attributes)
            {
                Console.WriteLine($"- {auth.Name} (承認日: {auth.Date})");
            }
        }
    }
}
実行結果
ProjectModule の担当者一覧:
- 田中太郎 (承認日: 2024-05-01)
- 佐藤次郎 (承認日: 2024-06-15)

もし AllowMultiple = true を設定せずに同じ属性を2回記述すると、CS0579: 属性 ‘Author’ が重複しています というコンパイルエラーが発生します。

リフレクションを用いた複数属性の取得方法

複数の属性が付与された要素から、実行時にその情報を読み取るには「リフレクション(Reflection)」を使用します。

GetCustomAttributesメソッドの使用

単一の属性を取得する場合は GetCustomAttribute(単数形)を使用しますが、複数の属性、特に同じ型の属性が複数存在する可能性がある場合は GetCustomAttributes(複数形)を使用します。

このメソッドは object[] を返すため、必要に応じてキャストして利用します。

C#
// すべての属性を取得する場合
var allAttributes = typeof(MyClass).GetCustomAttributes(true);

// 特定の型(AuthorAttribute)だけをすべて取得する場合
var authorAttributes = (AuthorAttribute[])typeof(MyClass).GetCustomAttributes(typeof(AuthorAttribute), true);

特定の型のみを抽出する方法

LINQを活用すると、特定の条件に合致する属性だけを効率よく抽出できます。

C#
using System.Linq;

// クラスに付与された属性の中から、特定の条件を持つものを探す
var specificAuthor = typeof(ProjectModule)
    .GetCustomAttributes(typeof(AuthorAttribute), false)
    .Cast<AuthorAttribute>()
    .FirstOrDefault(a => a.Name == "田中太郎");

if (specificAuthor != null)
{
    Console.WriteLine("田中さんが担当に含まれています。");
}

C# 11以降の汎用属性(Generic Attributes)との組み合わせ

C# 11からは「汎用属性(Generic Attributes)」が導入されました。

これにより、属性の引数に typeof(T) を渡す代わりに、型パラメータを利用できるようになりました。

もちろん、この汎用属性も複数指定が可能です。

C#
// 汎用属性の定義
public class ValidatorAttribute<T> : Attribute where T : IValidator
{
    public Type ValidatorType => typeof(T);
}

// 複数指定の例
[ValidatorAttribute<StringValidator>]
[ValidatorAttribute<LengthValidator>]
public class RegistrationForm
{
    // ...
}

汎用属性を使用することで、型安全性が向上し、コードがよりクリーンになります。

複数のバリデーションルールを型安全に適用したい場合に非常に強力な機能です。

実践的なユースケース

複数属性の指定が実際にどのように役立つか、代表的な3つのシナリオを見ていきましょう。

ASP.NET Coreでのコントローラー定義

Web API開発では、エンドポイントの挙動を制御するために属性を多用します。

C#
[ApiController]
[Route("api/[controller]")]
[Authorize(Roles = "Admin")]
public class AdminController : ControllerBase
{
    [HttpGet("{id}")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public IActionResult GetAdminData(int id)
    {
        // 処理
        return Ok();
    }
}

ここでは、クラスレベルで「APIコントローラーであること」「ルートパス」「認可ルール」を指定し、メソッドレベルで「HTTPメソッド」「レスポンス形式」を複数組み合わせて定義しています。

Entity Framework Coreでのモデル定義

データベースのテーブル構造を定義する際にも、属性の複数指定は必須です。

C#
public class Product
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required]
    [MaxLength(100)]
    [Column("product_name")]
    public string Name { get; set; }
}

Name プロパティには「必須入力(Required)」「最大長(MaxLength)」「カラム名指定(Column)」の3つの属性が適用されています。

Web APIのバリデーション

入力チェックにおいても、複数の制約を課すことが一般的です。

C#
public class UserRegistration
{
    [Required]
    [EmailAddress]
    [Display(Name = "メールアドレス")]
    public string Email { get; set; }

    [Required]
    [StringLength(20, MinimumLength = 8)]
    [RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$")]
    public string Password { get; set; }
}

このように「値の形式」「長さ」「正規表現」などを重ねて指定することで、堅牢なバリデーションを簡潔に記述できます。

属性使用時の注意点とベストプラクティス

属性を複数指定する際には、以下の点に注意してください。

順序に依存しない設計

C#では属性の適用順序が保証されず、リフレクションで取得する配列の順序も実行環境やコンパイラで変化します。

したがって、「A属性がB属性より先に実行される」など順序依存のロジックは避けるべきです。

可読性を優先する

あまりにも多くの属性が一つの要素に集中するとコードの本質的なロジックが埋もれます。

その場合は、カスタム属性で複数の属性を内部にまとめるか、Fluent API(例: Entity Framework の設定)への移行を検討してください。

AllowMultiple の扱いに注意

自作属性で AllowMultiple = true を設定する場合、それを読み取る側のロジックが複数(配列)で返ってくることを正しく想定しているか確認してください。

単一取得の GetCustomAttribute を使うと重複時に例外が発生するため、GetCustomAttributes や配列受け取りを使うなどの対策が必要です。

名前の重複を避ける

属性クラスは慣習として末尾に Attribute を付けますが、利用時は省略可能です。

ただし、異なる名前空間に同名の属性が存在する場合は、完全修飾名(例: Namespace.AttributeName)を使うか、エイリアス(例: using Alias = Namespace.AttributeName;)を設定して衝突を回避してください。

まとめ

C#における属性の複数指定は、宣言的でクリーンなコードを書くための基礎技術です。

独立したブラケット [] で一つずつ記述する方法が一般的であり、可読性やメンテナンス性の面で優れています。

また、自作属性において同じ種類の属性を複数回適用したい場合は、AttributeUsageAllowMultiple = true 設定が鍵となります。

これにリフレクションを用いた取得処理を組み合わせることで、柔軟なメタデータ管理が可能になります。

C# 11以降の汎用属性なども取り入れつつ、適切な属性の使い分けをマスターすることで、ASP.NET CoreやEF Coreといった強力なフレームワークのポテンシャルを最大限に引き出した開発が行えるようになるでしょう。

今後の開発において、属性によるメタデータ定義を整理する際の参考にしてください。