プロパティを高速検索する FastMember の使い方

クラスのプロパティ名を検索して値を Get したい、しかも高速で、という時の方法の Tips です。

## リフレクションのサンプル

クラスのプロパティ名を使っての操作はリフレクションと呼ばれていて、C# については Microsoft 社による解説もあります。
https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/concepts/reflection

Person というクラスを使った、プロパティ名を引数にして値を出力するサンプルです。

```c#:リフレクションサンプル
using System;
using System.Reflection;

static Dictionary<string, object?> DictionaryOfProperties(object obj)
{
    return obj.GetType()
        .GetProperties()
        .Where(x => x.CanRead)
        .ToDictionary(pi => pi.Name, pi => pi.GetValue(obj));
}

var d = DictionaryOfProperties(new Person());
Console.WriteLine(d["Name"]);
Console.WriteLine(d["Age"]);
Console.WriteLine(d["Height"]);

public class Person
{
    public string Name { get; set; } = "お名前";
    public int Age { get; set; } = 32;
    public int Height{ get; set; } = 170;
}
```

```bat:実行結果
お名前
32
170
```

存在しないプロパティ名を指定すると例外エラーが発生します。

## FastMember のサンプル

このリフレクションでの値参照を高速にする FastMember を使ってみます。
プログラムを動かされる場合は、Nuget で FastMember を Get しておいてください。

```c#:FastMemberのサンプル
using System;
using System.Reflection;
using FastMember;

static Dictionary<string, object> DictionaryOfProperties2(object obj)
{
    var type = FastMember.TypeAccessor.Create(obj.GetType());
    return type.GetMembers().ToDictionary(x => x.Name, x => type[obj, x.Name]);
}

var d = DictionaryOfProperties2(new Person());
Console.WriteLine(d["Age"]);
Console.WriteLine(d["Name"]);
Console.WriteLine(d["Height"]);

public class Person
{
    public string Name { get; set; } = "お名前";
    public int Age { get; set; } = 32;
    public int Height{ get; set; } = 170;
}
```

```bat:実行結果
お名前
32
170
```

全く同じ結果になりました。

## 速度比較

リフレクションと FastMember との速度比較をしてみます。
よくあるループを回してかかる時間を測るやつです。

```c#:比較プログラム
using System;
using System.Reflection;
using FastMember;

static Dictionary<string, object?> DictionaryOfProperties(object obj)
{
    return obj.GetType()
        .GetProperties()
        .Where(x => x.CanRead)
        .ToDictionary(pi => pi.Name, pi => pi.GetValue(obj));
}

static Dictionary<string, object> DictionaryOfProperties2(object obj)
{
    var type = FastMember.TypeAccessor.Create(obj.GetType());
    return type.GetMembers().ToDictionary(x => x.Name, x => type[obj, x.Name]);
}

int max = 1000000; // ループ回数

var d = DictionaryOfProperties(new Person());

var s1 = DateTime.Now;
for(int i = 0; i < max; i++)
{
    var x = d["Name"];
    var y = d["Age"];
    var z = d["Height"];
}
var s2 = DateTime.Now;
TimeSpan sp1 = s2 - s1;
Console.WriteLine(sp1.TotalMilliseconds);

var d2 = DictionaryOfProperties2(new Person());

var s3 = DateTime.Now;
for (int i = 0; i < max; i++)
{
    var x = d2["Name"];
    var y = d2["Age"];
    var z = d2["Height"];
}
var s4 = DateTime.Now;
TimeSpan sp2 = s4 - s3;
Console.WriteLine(sp2.TotalMilliseconds);

public class Person
{
    public string Name { get; set; } = "";
    public int Age { get; set; }
    public int Height { get; set; }

    public Person()
    {
        Random r = new Random();

        for (int i = 0; i < 10; i++)
        {
            char c1 = (char)(97 + r.Next(0, 24));
            Name += c1;
        }

        Age = (int)r.Next(10, 32);
        Height = (int)r.Next(150, 190);
    }
}
```

FastMember の方がリフレクションよりも処理が速いことが分かります。
とくに1000回程度までであれば圧倒的に速いです。

| 回数 | リフレクション(ms) | FastMember(ms) |
|-:|--:|--:|
|10回 | 4.9723 | 0.003 |
|100回 | 9.4437 | 0.0083 |
|1,000回 | 9.0036 | 0.0667 |
|10,000回 | 9.9663 | 0.6479 |
|100,000回 | 17.4046 | 6.5293 |
|100,000 回 | 116.0701 | 75.3068 |