属性の基本概念
属性は本質的にクラスであり、要素に属性を付与することでその属性クラスのインスタンスが生成されます。属性を使用することで、クラス、メソッド、構造体、列挙型、コンポーネントなどの要素にラベルを付けることができ、これらの要素に共通の特性を持たせることが可能になります。例えば、メソッドの例外処理において、データベース操作やファイル操作など例外が発生しやすい箇所で従来のtry...catch文を使用する代わりに、属性を利用することでコードの簡潔化が実現できます。例外処理専用のMyExceptionHandlerAttributeクラスを定義し、AOPによる横断的関心事の処理機能を追加することで、例外が発生する可能性のあるメソッドに\[MyExceptionHandler\]という属性を付与するだけで自動的な例外処理が可能になります。
公式定義では、属性は実行時にプログラム内の各種要素(クラス、メソッド、構造体、列挙型、コンポーネントなど)の動作情報を伝達するための宣言的ラベルとして機能します。属性を使用することでプログラムに宣言的情報を追加できます。宣言的ラベルは適用対象の要素の前に配置される角括弧(\[ \])によって表現されます。属性はコンパイラ命令、コメント、説明、メソッド、クラスなどの他の情報を含むメタデータの追加に使用されます。.NET Frameworkは予め定義された属性とカスタム属性の2つのタイプを提供しています。
属性の構文
属性の構文は以下の通りです:
[attribute(positional_parameters, name_parameter = value, ...)] element属性名と値は角括弧内で指定され、適用対象の要素の前に配置されます。positional_parametersは必須情報を規定し、name_parameterはオプション情報を規定します。
予め定義された属性
.NET Frameworkは以下の3つの予め定義された属性を提供しています:
- AttributeUsage
- Conditional
- Obsolete
AttributeUsage属性
予め定義されたAttributeUsage属性は、カスタム属性クラスの使用方法を記述します。これは属性を適用できる項目の種類を規定します。構文は以下の通りです:
[AttributeUsage(
validon,//パラメータvalidonは属性を配置できる言語要素を規定します。これはAttributeTargets列挙子の値の組み合わせです。デフォルト値はAttributeTargets.Allです。
AllowMultiple=allowmultiple,//パラメータallowmultiple(オプション)は属性のAllowMultipleプロパティに真偽値を提供します。trueの場合、属性は複数回使用可能です。デフォルト値はfalse(単独使用)です。
Inherited=inherited//パラメータinherited(オプション)は属性のInheritedプロパティに真偽値を提供します。trueの場合、属性は派生クラスに継承されます。デフォルト値はfalse(継承されない)です。
)]
using System;
namespace AttributeExample
{
class Application
{
static void Main(string[] args)
{
//リフレクションを使用してMyCustomAttributeを確認
Type type = typeof(TestClass);
var attributes = type.GetCustomAttributes(typeof(CustomInfoAttribute), false);
foreach (CustomInfoAttribute attr in attributes)
{
Console.WriteLine("Title:{0}", attr.Title);
Console.WriteLine("Version:{0}", attr.Version);
}
Console.ReadKey();
}
}
/// <summary>
/// AttributeUsageを使用したカスタム属性CustomInfoAttribute
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public class CustomInfoAttribute : Attribute
{
public CustomInfoAttribute(string title, int version)
{
this.Title = title;
this.Version = version;
}
public string Title { get; set; }
public int Version { get; set; }
}
/// <summary>
/// カスタム属性の使用
/// </summary>
[CustomInfo("ApplicationCore", 1)]
[CustomInfo("BusinessLayer", 2)]
public class TestClass
{
}
}
Conditional属性
Conditional属性はプロジェクトで頻繁に使用される重要な予め定義された属性です。#if/#endifは同じコードから異なるビルド結果を生成するためによく使用されますが、実際の運用では必ずしも望ましくありません。なぜなら、これらは乱用されやすく、コードの理解やデバッグが困難になるからです。例:
static void ProcessData()
{
#if DEBUG
Console.WriteLine("Processing data in debug mode.");
#endif
}
このコードはリリース環境でコンパイルするとProcessData()メソッドは空になります。リリースバージョンではProcessData()は何も実行しませんが、メソッドの読み込み、JITコンパイル、呼び出しには依然としてオーバーヘッドがあります。C#ではConditional属性が追加され、特定の環境設定に基づいてメソッドが呼び出されるかどうかを識別します。この条件付きコンパイルの記述方法は#if/#endifよりも明確です。この属性は、指定されたプリプロセッサ識別子に依存する条件付きメソッドを示します。これにより、DebugまたはTraceなどの指定された値に応じてメソッド呼び出しの条件付きコンパイルが行われます。構文は\[Conditional(conditionalSymbol)\]です。
Conditional属性を使用する場合、DEBUG環境変数が定義されていなくてもProcessData()メソッドはアセンブリにコンパイルされます。効率的に見えませんが、実際にはディスクスペースが少し増えるだけです。呼び出されない限り、ProcessData()はメモリに読み込まれることもなく、JITコンパイルもされません。複数のConditional属性を適用する場合、それらの論理関係は「OR」になります。
[Conditional("DEBUG"),Conditional("TRACE")]
static void ProcessData() { Console.WriteLine("Processing in debug mode."); }
「AND」関係の構成を作成したい場合は#if/#endifを使用してください。
static void ProcessData() { #if DEBUG && RELEASE Console.WriteLine("Processing in combined mode."); #endif }
Obsolete属性
この予め定義された属性は使用すべきでないプログラム要素を示します。コンパイラに対して特定のターゲット要素を破棄するよう通知できます。例えば、クラスに新しいメソッドが導入されたが古いメソッドを維持したい場合、新しいメソッドではなく古いメソッドを使用すべきであることを示すメッセージを表示することで、obsolete(非推奨)としてマークできます。構文は以下の通りです:
[Obsolete(
message
)]
[Obsolete(
message,//パラメータmessageは文字列で、項目が非推奨である理由と代替手段を記述します
iserror//パラメータiserrorは真偽値です。trueの場合、コンパイラはその項目の使用をエラーとして扱います。デフォルト値はfalse(コンパイラは警告を生成)です
)]
using System;
public class SampleClass
{
[Obsolete("OldFunctionは使用しないでください。代わりにNewFunctionを使用してください", true)]
static void OldFunction()
{
Console.WriteLine("This is the deprecated function");
}
static void NewFunction()
{
Console.WriteLine("This is the new function");
}
public static void Main()
{
NewFunction();
}
}
このプログラムをコンパイルしようとすると、コンパイラは「OldFunctionは使用しないでください。代わりにNewFunctionを使用してください」というエラーメッセージを表示します。
カスタム属性の作成
.NET Frameworkでは、実行時に取得可能な宣言的情報を格納するためのカスタム属性の作成を許可しています。カスタム属性の作成と使用には4つのステップがあります:
- カスタム属性の宣言
- カスタム属性の構築
- ターゲットプログラム要素へのカスタム属性の適用
- リフレクションによる属性へのアクセス
カスタム属性の宣言
新しいカスタム属性はSystem.Attributeクラスから派生させる必要があります。例:
// クラスとそのメンバーに割り当てられるカスタム属性DeveloperInfo
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]
public class DeveloperInfoAttribute : Attribute
{}
カスタム属性の構築
DeveloperInfoAttributeという名前のカスタム属性を作成しましょう。この属性はデバッガが得た情報を格納します。以下の情報を保存します:
- バグのコード番号
- バグを特定した開発者の名前
- コードの最終レビュー日
- 開発者によるメモを格納する文字列メッセージ
私たちのDeveloperInfoAttributeクラスは最初の3つの情報を格納するためのプライベートプロパティと、メッセージを格納するためのパブリックプロパティを備えています。そのため、バグ番号、開発者名、レビュー日はDeveloperInfoAttributeクラスの必須の位置指定パラメータとなり、メッセージはオプションの名前付きパラメータとなります。各属性は少なくとも1つのコンストラクタが必要です。必須の位置指定パラメータはコンストラクタ経由で渡されます。以下のコードはDeveloperInfoAttributeクラスを示しています:
// クラスとそのメンバーに割り当てられるカスタム属性DeveloperInfo
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]
public class DeveloperInfoAttribute : System.Attribute
{
private int issueId;
private string programmer;
private string reviewDate;
public string comment;
public DeveloperInfoAttribute(int id, string dev, string date)
{
this.issueId = id;
this.programmer = dev;
this.reviewDate = date;
}
public int IssueId
{
get
{
return issueId;
}
}
public string Programmer
{
get
{
return programmer;
}
}
public string ReviewDate
{
get
{
return reviewDate;
}
}
public string Comment
{
get
{
return comment;
}
set
{
comment = value;
}
}
}
カスタム属性の適用
属性を適用するには、そのターゲットの直前に属性を配置します:
[DeveloperInfoAttribute (12, "Yuki Tanaka", "2023-05-15", Comment = "Type conversion issue")]
[DeveloperInfoAttribute (18, "Hiroshi Sato", "2023-06-20", Comment = "Unused parameter")]
class Shape
{
// メンバー変数
protected double height;
protected double width;
public Shape(double h, double w)
{
height = h;
width = w;
}
[DeveloperInfoAttribute (22, "Yuki Tanaka", "2023-07-10",
Comment = "Return value validation")]
public double CalculateArea()
{
return height * width;
}
[DeveloperInfoAttribute (23, "Yuki Tanaka", "2023-07-10")]
public void ShowDetails()
{
Console.WriteLine("Height: {0}", height);
Console.WriteLine("Width: {0}", width);
Console.WriteLine("Area: {0}", CalculateArea());
}
}