C#で構造体やクラスに定義した属性の値を取得する

構造体やクラスのプロパティに定義した属性値を取得する方法を説明します。
例として、下記の構造体のプロパティに付加された 『FixedText』 の属性値を取得します。

FixedText属性を持った構造体
public struct Todofuken
{
    /// <summary>
    /// 都道府県コード
    /// </summary>
    [FixedText(Len = 2, PadChar = '0']
    public int Code { set; get; }

    /// <summary>
    /// 都道府県名
    /// </summary>
    [FixedText(Len = 10, PadChar = ' ']
    public string Name { set; get; }
}

なお、属性クラスは下記になります。詳しくは前回の記事をご覧ください。

FixedText属性クラス
[AttributeUsage(AttributeTargets.Property)]
public class FixedTextAttribute : System.Attribute
{
    public int Len { set; get; }
    public char PadChar { set; get; }
}

プロパティの属性を取得するには、プロパティのメタデータにアクセスするためのクラス 『System.Reflection.PropertyInfo』 が必要になります。
上記構造体の Code プロパティに対する PropertyInfo の取得方法は以下のようになります。

// Codeプロパティの PropertyInfo が必要なので取得する。Type の GetProperty メソッドで取得する
System.Reflection.PropertyInfo propertyInfo =  typeof(Todofuken).GetProperty("Code");

取得した PropertyInfo を使用して、以下のコードで FixedText 属性を取得します。

// Attribute の GetCustomAttribute メソッドでプロパティに設定された属性を取得する
FixedTextAttribute attribute = Attribute.GetCustomAttribute(
        propertyInfo, typeof(FixedTextAttribute)) as FixedTextAttribute;

あとは FixedText 属性のそれぞれの項目を取得するだけです。

// 属性に設定された値を取得する
Console.WriteLine(
    string.Format("Codeプロパティの属性値: Len={0} PadChar={1}"
        ,attribute.Len, attribute.PadChar));

上のコードをまとめると以下のようになります

/// <summary>
/// Todofuken構造体のプロパティ『Code』に設定された FixedText属性の値を取得する
/// </summary>
private void getAttributeValue_CodeProperty()
{
    // Codeプロパティの PropertyInfo が必要なので取得する。Type の GetProperty メソッドで取得する
    System.Reflection.PropertyInfo propertyInfo = typeof(Todofuken).GetProperty("Code");

    // Attribute の GetCustomAttribute メソッドで プロパティに設定された属性を取得する
    FixedTextAttribute attribute = Attribute.GetCustomAttribute(
        propertyInfo, typeof(FixedTextAttribute)) as FixedTextAttribute;

    // 属性に設定された値を取得する
    Console.WriteLine(
        string.Format("Codeプロパティの属性値: Len={0} PadChar={1}"
            ,attribute.Len, attribute.PadChar));
}

同様に Name プロパティに設定された属性を以下のコードで取得します。

/// <summary>
/// Todofuken構造体のプロパティ『Name』に設定された FixedText属性の値を取得する
/// </summary>
private void getAttributeValue_NameProperty()
{
    // Nameプロパティの PropertyInfo が必要なので取得する。Type の GetProperty メソッドで取得する
    System.Reflection.PropertyInfo propertyInfo = typeof(Todofuken).GetProperty("Name");

    // Attribute の GetCustomAttribute メソッドで プロパティに設定された属性を取得する
    FixedTextAttribute attribute = Attribute.GetCustomAttribute(
        propertyInfo, typeof(FixedTextAttribute)) as FixedTextAttribute;

    // 属性に設定された値を取得する
    Console.WriteLine(
        string.Format("Nameプロパティの属性値: Len={0} PadChar={1}"
            ,attribute.Len, attribute.PadChar));
}

このやりかただと、構造体のプロパティ名を予め知っておく必要がありますので、汎用化するには難があります。
そこで、以下のようにして構造体のプロパティすべてを取得する方法に変更することにします。

/// <summary>
/// 構造体に設定された属性の値を取得する
/// </summary>
public void getAttributeValue()
{
    foreach (PropertyInfo propertyInfo in typeof(Todofuken).GetProperties())
    {
        var attribute = Attribute.GetCustomAttribute(
            propertyInfo, typeof(FixedTextAttribute)) as FixedTextAttribute;

        //属性が定義されたプロパティだけを参照するため、fixedAttrがnullなら処理の対象外
        if (attribute != null)
        {
            Console.WriteLine(
                string.Format("{0}プロパティの属性値: Len={1} PadChar={2}"
                , propertyInfo.Name, attribute.Len, attribute.PadChar)
                );
        }
    }
}

更に Tofofuken 構造体以外の構造体やクラスでも使用できるように下記のように改造します。

/// <summary>
/// 構造体やクラスに設定された属性の値を取得する
/// </summary>
/// <typeparam name="T">属性が定義された構造体またはクラス</typeparam>
public void getAttributeValue<T>()
{
    foreach (PropertyInfo propertyInfo in typeof(T).GetProperties())
    {
        var attribute = Attribute.GetCustomAttribute(
            propertyInfo, typeof(FixedTextAttribute)) as FixedTextAttribute;

        //属性が定義されたプロパティだけを参照するため、fixedAttrがnullなら処理の対象外
        if(attribute != null)
        {
            Console.WriteLine(
                string.Format("{0}プロパティの属性値: Len={1} PadChar={2}"
                    , propertyInfo.Name, attribute.Len, attribute.PadChar)
                );
        }
    }
}

この関数は以下のように呼び出します。

getAttributeValue<Todofuken>();

C#で属性を作成する

属性を作成する

下記に示すような構造体のプロパティに付加する属性 『FixedText』 を作成していきます。

FixedText属性を持った構造体
public struct Todofuken
{
    /// <summary>
    /// 都道府県コード
    /// </summary>
    [FixedText(Len = 2, PadChar = '0']
    public int Code { set; get; }

    /// <summary>
    /// 都道府県名
    /// </summary>
    [FixedText(Len = 10, PadChar = ' ']
    public string Name { set; get; }
}

1.まずは System.Attribute を継承したクラスを作成します。ここで作成したクラス名が、属性名となります。

public class FixedTextAttribute : System.Attribute
{
}

クラス名に Attribute を付加した場合、Attributeを除いた属性名も有効になります。
つまり、下記の属性名は両方とも有効で、同じ属性を表します。

  • [FixedText]

  • [FixedTextAttribute]

クラス名に Attribute をつけなかった場合、先頭に@をつけた属性名も有効になります。
この場合、下記の属性名が有効になります。

  • [FixedText]

  • [@FixedText]

2.作成する属性がプロパティに対する属性であることを指定します。

AttributeUsage属性を用いて、何に対する属性なのかを指定します。
『何に対する』とは、例えば次のようなものがあります。(これら以外にもあります。)

  • クラスに対する属性

  • 構造体に対する属性

  • フィールドに対する属性

  • プロパティに対する属性

  • メソッドに対する属性

プロパティの場合は、AttributeUsege 属性に AttributeTargets.Property を指定します。

[AttributeUsage(AttributeTargets.Property)]
public class FixedTextAttribute : System.Attribute
{
}

複数の項目に対する属性であることを示す場合は、| で結合します。
例えば、プロパティとフィールドの両方に対する属性であることを指定する場合、次のようになります。

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class FixedTextAttribute : System.Attribute
{
}

3.属性に項目を持たせる

FixedText属性は、長さ(Len)と付加文字(PadChar)の2つの項目を持っています。
これらをプロパティとして追加します。

[AttributeUsage(AttributeTargets.Property)]
public class FixedTextAttribute : System.Attribute
{
    public int Len { set; get; }
    public char PadChar { set; get; }
}

以上で、属性の作成は完了です。
次は、クラスや構造体に定義された属性を取得する方法を掲載していきます。


C#の属性という便利な機能

例えば、次のような固定長のテキストを出力する仕様があるとします。

Table 1. 固定長項目
No 列名 長さ 出力形式

1

都道府県コード

2

前ゼロ埋め

2

都道府県名

10

後ろ半角スペース埋め

また、以下の構造体に都道府県のデータが格納されているとします。

構造体
public struct Todofuken
{
    /// <summary>
    /// 都道府県コード
    /// </summary>
    public int Code { set; get; }

    /// <summary>
    /// 都道府県名
    /// </summary>
    public string Name { set; get; }
}

仕様を満たすため、以下のようなコーディングをすると思います。
(あくまでも一例です)

/// <summary>
/// 引数の都道府県データを固定長のテキストにして返す
/// </summary>
/// <param name="data">都道府県データ</param>
/// <returns>固定長テキスト</returns>
private string getFixedText(Todofuken data)
{
    StringBuilder sb = new StringBuilder();

    //都道府県コード 前0埋め2桁
    sb.Append(data.Code.ToString().PadLeft(2, '0'));

    //都道府県名後ろスペース埋め
    sb.Append(data.Name.PadRight(10));

    return sb.ToString();
}

これだと 『どの項目が何桁で、桁埋めで使用する文字は何か』 という情報がソースの中に埋もれてしまいます。
そこで属性の出番です。
属性の機能を使用すると 『どの項目が何桁で、桁埋めで使用する文字は何か』 という情報を構造体に記述できるようになります。
ソースを追う必要がなくなり、『どの項目が何桁で、桁埋めで使用する文字は何か』が一目瞭然になります。

もちろん、属性を利用するためにはいろいろとコーディングが必要になります。
その内容は次回以降に掲載します。
固定長情報を属性に記述するようにした構造体
public struct Todofuken
{
    /// <summary>
    /// 都道府県コード
    /// </summary>
    [FixedText(Len = 2, PadChar = '0', PadType = EPadType.Before)]
    public int Code { set; get; }

    /// <summary>
    /// 都道府県名
    /// </summary>
    [FixedText(Len = 10, PadChar = ' ', PadType = EPadType.After)]
    public string Name { set; get; }
}