カツラ検出プログラムで学ぶ C# Attributes

今日も自分の学びに対するメモです。Azure Functions を書いていると必ず出てくる Attributes ですが、カスタムの Attributes を書くときにどんなコードになるのか知りたかったので調べて書いてみました。

<img width="1367" alt="GH.png" src="https://qiita-image-store.s3.amazonaws.com/0/3470/6d3a3184-7dac-6d5b-72a6-be395ede6939.png">


題材として、被告人がいて、自分がズラかどうかを告白するのですが、Attributes によってそれを見破るプログラムを作ってみました。

# カスタムAttributes の定義

Attributes の定義は次のようになっていて。単なる普通のクラスです。コンストラクタを書いておくと、クラスやメソッドに対して、Attributes をつけられるようになる様子です。　AttributeUsage でAttribute であることを明確にします。Attribute 名は、Attribute の接尾句を省略したものになるので、ここでは、`[GHDetect]` という Attribute が使えるようになります。

```csharp
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method,
        AllowMultiple = true, Inherited = false)]
    public class GHDetectAttribute : Attribute
    {
        private string maker;
        private bool isWig;
        public GHDetectAttribute(bool isWig, string maker)
        {
            this.maker = maker;
            this.isWig = isWig;
        }
        public string Maker {  get { return this.maker; } }
        public bool IsWig { get { return this.isWig; } }
    }

```

# カスタムAttributes の適用

全く同じクラスを定義して、ほぼ同じ結果を返すメソッドを定義した２つのクラスを定義します。片方には、カツラをtrue にし、そのカツラのメーカー名の情報を持たせておきます。一方は、カツラではないということにしておきましょう。

```csharp
  public interface Accused
    {
        void Execute();
    }

    [GHDetect(true, "アデランス")]
    public class Takeru : Accused
    {
        public void Execute()
        {
            Console.WriteLine("[Takeru] 私ははげていません。");
        }
    }
    [GHDetect(false, null)]
    public class Tsuyoshi : Accused
    {
        public void Execute()
        {
            Console.WriteLine("[Tsuyoshi] 私ははげていません。");
        }
    }
```

# Attribute の取得

クラスの型がわかれば、定義された Attribute が取得できます。ちなみに、メソッドに定義した場合は、リフレクションを使ってメソッドを取得してから、Attributes が取れる様子です。まずは簡単のためクラスのみでやってみます。 `MemberInfo` を渡すと、Attribute を返すようにしています。ちなみに、Attribute[] を返すようにもできます。（複数のアトリビュートがある場合）。クラスのタイプだったり、MethodInfo を引数として渡すことができます。

```csharp
        static Attribute DetectGH(MemberInfo info)
        {
            Attribute detector = Attribute.GetCustomAttribute(
                info, typeof(GHDetectAttribute));
            return detector;
        }
```

Attribute が取れれば、これは単なるクラスですので、値を取得してロジックを書きます。

```csharp
    static void Execute(Accused accused)
        {
            var accusedAttribute = DetectGH(accused.GetType()) as GHDetectAttribute;
            if (accusedAttribute.IsWig)
            {
                accused.Execute();
                Console.WriteLine($"カツラが検出されました。　メーカー: {accusedAttribute.Maker}");
            } else
            {
                accused.Execute();
                Console.WriteLine("本当に地毛のようです。");
            }
        }
```

これでカツラかどうかを判別できるようになるはずです。では次のメインを書いて実行。

```csharp
        static void Main(string[] args)
        {
            Accused tsuyoshi = new Tsuyoshi();
            Execute(tsuyoshi);
            Console.ReadLine();
            Accused takeru = new Takeru();
            Execute(takeru);
            Console.ReadLine();
            
        }
```
# 実行

```
[Tsuyoshi] 私ははげていません。
本当に地毛のようです。

[Takeru] 私ははげていません。
カツラが検出されました。　メーカー: アデランス
```

めでたしめでたし。

# Resource 

* [ C# によるプログラミング入門 [動的な処理] 属性 ](http://ufcpp.net/study/csharp/sp_attribute.html)
* [ C# によるプログラミング入門 [その他] プリプロセス ](http://ufcpp.net/study/csharp/sp_preprocess.html)
* [ サンプルプログラム ](https://github.com/TsuyoshiUshio/ZuraSample)