C#における属性(Attribute)とは、プログラムの要素に対して追加のメタデータを宣言的に付与するための仕組みです。

クラス、メソッド、プロパティ、あるいはアセンブリ全体に対して、実行時の振る舞いやコンパイラの動作を制御する情報を付加できます。

属性を適切に活用することで、コードの可読性を高めるだけでなく、共通処理の自動化や外部ライブラリとの連携をスムーズに行うことが可能になります。

本記事では、C#で頻繁に使用される標準属性の一覧から、具体的な使い方、さらには独自属性の作成方法まで、開発現場で役立つ知識を網羅的に解説します。

C#の属性(Attribute)とは何か

属性は、C#プログラムの要素(型、メンバー、アセンブリなど)に関する補足的な情報を記述するためのものです。

これらは「メタデータ」としてコンパイル後のバイナリに格納され、実行時に「リフレクション」という機能を通じて参照したり、コンパイル時にコンパイラによって特定の処理をトリガーしたりするために使用されます。

属性の基本構文

属性は、対象となる要素の直前に [] (角括弧)で囲んで記述します。

多くの属性は、クラス名から Attribute というサフィックスを除いた名前で呼び出すことができます。

C#
using System;

// クラスに対して属性を付与
[Serializable]
public class SampleClass
{
    // メソッドに対して属性を付与
    [Obsolete("このメソッドは古いバージョンです。NewMethodを使用してください。")]
    public void OldMethod()
    {
        Console.WriteLine("古いメソッドが実行されました。");
    }

    public void NewMethod()
    {
        Console.WriteLine("新しいメソッドが実行されました。");
    }
}

上記の例では、[Serializable] 属性によってクラスがシリアル化可能であることを示し、[Obsolete] 属性によってメソッドが非推奨であることを示しています。

開発でよく使われる標準属性一覧

C#(.NET)には、あらかじめ多くの標準属性が用意されています。

これらを理解しておくことで、車輪の再発明を防ぎ、標準的なコーディング規約に則った開発が可能になります。

一般的な制御・情報付加属性

日常的な開発で最も目にすることが多い属性群です。

属性名説明
[Obsolete]型やメンバーが非推奨であることを示し、コンパイル時に警告またはエラーを出します。
[Serializable]そのクラスのインスタンスがシリアル化可能であることを示します。
[NonSerialized][Serializable] なクラス内で、シリアル化の対象から除外するフィールドを指定します。
[Conditional]特定の条件付きコンパイル記号(DEBUGなど)が定義されている場合のみ実行されるようにします。
[Flags]列挙型(enum)をビットフラグとして扱えるようにします。

[Obsolete] 属性の使い方

この属性は、コードの移行期間において非常に重要です。

第2引数に true を渡すと、警告ではなくコンパイルエラーとして扱うこともできます。

C#
public class ApiClient
{
    [Obsolete("セキュリティ上の理由により使用禁止です。", true)]
    public void ConnectInsecure()
    {
        // 接続処理
    }
}

コンパイラへの指示とデバッグ属性

デバッグ効率の向上や、コンパイラの最適化に影響を与える属性です。

属性名説明
[DebuggerStepThrough]デバッガーがこのメソッド内をステップ実行せずに通過するようにします。
[MethodImpl]メソッドの実装の詳細(インライン化の強制など)を指定します。
[CallerMemberName]メソッドを呼び出した元のメンバー名を自動的に取得します。
[CallerFilePath]メソッドを呼び出したソースファイルのパスを取得します。
[CallerLineNumber]メソッドを呼び出した行番号を取得します。

呼び出し元情報の取得例

INotifyPropertyChanged インターフェースの実装などで、プロパティ名をハードコードせずに取得する際によく使われます。

C#
using System;
using System.Runtime.CompilerServices;

public class Logger
{
    public void Log(string message, 
        [CallerMemberName] string memberName = "",
        [CallerFilePath] string filePath = "",
        [CallerLineNumber] int lineNumber = 0)
    {
        Console.WriteLine($"[LOG] {message}");
        Console.WriteLine($"  Member: {memberName}");
        Console.WriteLine($"  File: {filePath}");
        Console.WriteLine($"  Line: {lineNumber}");
    }
}

class Program
{
    static void Main()
    {
        var logger = new Logger();
        logger.Log("デバッグメッセージです。");
    }
}
実行結果
[LOG] デバッグメッセージです。
  Member: Main
  File: C:\Path\To\Program.cs
  Line: 24

データ検証(Data Annotations)属性

ASP.NET Core や Entity Framework を使用する際、モデルの検証ルールを定義するために System.ComponentModel.DataAnnotations 名前空間の属性が多用されます。

主要な検証属性

属性名説明
[Required]値が必須であることを指定します。
[StringLength]文字列の最大長および最小長を指定します。
[Range]数値が指定された範囲内にあることを確認します。
[RegularExpression]正規表現によるパターンマッチングを行います。
[EmailAddress]有効なメールアドレス形式かどうかを検証します。
[Key]Entity Framework で主キーであることを示します。

モデル定義での使用例

現代のC#(C# 11以降)では、required 修飾子と組み合わせて、言語レベルの制約とバリデーション属性を使い分けるのが一般的です。

C#
using System.ComponentModel.DataAnnotations;

public class UserRegistrationModel
{
    [Required(ErrorMessage = "ユーザー名は必須です")]
    [StringLength(20, MinimumLength = 3, ErrorMessage = "ユーザー名は3〜20文字で入力してください")]
    public string Username { get; set; } = string.Empty;

    [Required]
    [EmailAddress(ErrorMessage = "不正なメールアドレス形式です")]
    public string Email { get; set; } = string.Empty;

    [Range(18, 120, ErrorMessage = "18歳以上である必要があります")]
    public int Age { get; set; }
}

シリアル化と外部連携の属性

JSONやXMLへの変換、あるいは外部のDLL(アンマネージドコード)を呼び出す際に使用する属性です。

JSONシリアル化(System.Text.Json)

モダンな.NET開発では System.Text.Json が標準的に使われます。

属性名説明
[JsonPropertyName]JSON出力時のプロパティ名を指定します。
[JsonIgnore]シリアル化の対象から除外します。
[JsonInclude]非公開プロパティをシリアル化に含めます。
[JsonConstructor]デシリアル化時に使用するコンストラクタを指定します。
C#
using System.Text.Json;
using System.Text.Json.Serialization;

public class Product
{
    [JsonPropertyName("product_id")]
    public int Id { get; set; }

    public string Name { get; set; } = string.Empty;

    [JsonIgnore]
    public string InternalSecret { get; set; } = "Secret";
}

P/Invoke(プラットフォーム呼び出し)

Windows API などのネイティブライブラリを呼び出す際に不可欠な属性です。

属性名説明
[DllImport]外部の DLL ファイルとその関数をインポートします。
[StructLayout]構造体のメモリ上の配置(パディングなど)を制御します。
[MarshalAs]マネージド型とアンマネージド型の間のデータ変換方法を指定します。
C#
using System;
using System.Runtime.InteropServices;

public class NativeMethods
{
    // Windows APIのMessageBoxを呼び出す
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
}

カスタム属性の作成と利用

標準属性だけでなく、自分で独自の属性を作成してプログラムに独自のロジックを組み込むことができます。

これは、フレームワーク自作や大規模なアプリケーションの共通処理(権限チェック、ログ出力など)で非常に強力な武器となります。

カスタム属性の定義方法

カスタム属性を作成するには、System.Attribute クラスを継承したクラスを作成します。

また、その属性をどこに適用できるかを制限するために [AttributeUsage] 属性を付与するのが一般的です。

C#
using System;

// クラスとメソッドに適用可能なカスタム属性
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class AuthorAttribute : Attribute
{
    public string Name { get; }
    public string Version { get; set; } = "1.0";

    public AuthorAttribute(string name)
    {
        Name = name;
    }
}

[Author("Taro Yamada", Version = "1.2")]
public class MyBusinessLogic
{
    // 処理内容
}

属性の情報をリフレクションで取得する

付与された属性は、実行時に リフレクション を使用して読み取ることができます。

これにより、特定の属性がついているクラスだけを自動で登録したり、特定のメソッドを実行したりする動的な仕組みを構築できます。

C#
using System;
using System.Linq;

public class AttributeInspector
{
    public static void PrintAuthorInfo(Type type)
    {
        // 型に付与された AuthorAttribute を取得
        var authAttr = (AuthorAttribute?)Attribute.GetCustomAttribute(type, typeof(AuthorAttribute));

        if (authAttr != null)
        {
            Console.WriteLine($"Type: {type.Name}");
            Console.WriteLine($"Author: {authAttr.Name}");
            Console.WriteLine($"Version: {authAttr.Version}");
        }
        else
        {
            Console.WriteLine($"{type.Name} には Author 属性がありません。");
        }
    }
}

class Program
{
    static void Main()
    {
        AttributeInspector.PrintAuthorInfo(typeof(MyBusinessLogic));
    }
}
実行結果
Type: MyBusinessLogic
Author: Taro Yamada
Version: 1.2

モダンC#における新しい属性

C#の進化に伴い、パフォーマンス最適化やコード品質向上のための新しい属性が追加され続けています。

Null許容参照型に関する属性

C# 8.0以降、Null安全を強化するために System.Diagnostics.CodeAnalysis 名前空間に多くの属性が追加されました。

これらは静的解析ツールに対して、ある引数がNullを許容するか、あるいはメソッド終了時に非Nullであることが保証されるかなどを伝えます。

  • [AllowNull]: 入力としてNullを許可するが、実際にはNullでないかのように扱う。
  • [NotNull]: メソッドの戻り値や引数がNullでないことを保証する。
  • [MaybeNullWhen(true)]: メソッドが true を返す場合に、引数がNullである可能性があることを示す。

C# 11以降の注目属性

[SetsRequiredMembers]

C# 11で導入された required プロパティを持つクラスにおいて、コンストラクタ内で全ての必須メンバーを初期化していることをコンパイラに伝えるために使用されます。

C#
using System.Diagnostics.CodeAnalysis;

public class User
{
    public required string Id { get; init; }
    public required string Name { get; init; }

    [SetsRequiredMembers]
    public User(string id, string name)
    {
        Id = id;
        Name = name;
    }
}

これを付与しないと、オブジェクト初期化子を使わずにコンストラクタで初期化しようとした際、コンパイラが「必須プロパティが設定されていない可能性がある」と判断してエラーを出す場合があります。

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

属性は非常に便利ですが、誤った使い方はパフォーマンスの低下やコードの複雑化を招きます。

リフレクションのオーバーヘッドに注意する

実行時に属性を頻繁にリフレクションで取得するのは計算コストがかかります。

頻繁に参照する場合は、キャッシュ機構(例:Dictionaryやメモ化)を導入して取得回数を削減してください。

属性に複雑なロジックを持たせない

属性はあくまでメタデータの宣言に留め、属性クラスのコンストラクタプロパティの中で重い処理(I/O、長時間計算など)を行うのは避け、単純なデータ保持にしてください。

名前付けの慣習を守る

独自属性を作成する際は必ず末尾にAttributeを付けてください。

これにより他の開発者が属性であると認識しやすく、利用時にサフィックスを省略できるC#の恩恵を受けられます。

AttributeUsage を適切に設定する

属性が適用されるべきでない場所に適用されるのを防ぐため、必ず[AttributeUsage]を指定し、適用対象(例:クラス、プロパティなど)と重複許可の有無を明示してください。

まとめ

C#の属性は、コードに対する「注釈」以上の力を持つ強力なメタプログラミング機能です。

標準で用意されている属性を使いこなすことで、コンパイラの警告を制御したり、JSON通信の定義を簡潔にしたり、呼び出し元の情報を自動取得したりと、開発効率を劇的に向上させることができます。

特に、モダンなC#開発(ASP.NET CoreやEntity Framework Core、最新のC#言語仕様)においては、属性なしで開発を進めることはほぼ不可能です。

今回紹介した標準属性の一覧を参考に、まずは身近なコードに属性を取り入れてみてください。

また、プロジェクト独自のルールや定型処理が増えてきた際には、カスタム属性を自作してリフレクションで活用する手法も検討してみると、より洗練されたアーキテクチャへの一歩となるでしょう。

C#の属性は常に進化しており、新バージョンごとに便利な属性が追加されています。

公式ドキュメントや最新のリリースノートを定期的にチェックし、常に最適な属性の選択ができるよう知識をアップデートしていきましょう。