これで二回目です。前回はC#のLinqに関する軽い紹介を書きました。
今日のテーマは"privateなメンバーにアクセスしよう"です。

注意 見出しへのリンク

この記事ではSystem.Reflectionを使用してprivateなメンバーにアクセスする方法をお伝えします。ただ、自己責任でお願いします。(危ない操作ではありませんが、人のコードに対して行えば著作権侵害とかになりえるので。)

突然ですが問題です 見出しへのリンク

あなたは以下のようなクラスのインスタンスを与えられました。
このクラスにはどうやらfloat型の変数があってそれは秘密の値で初期化されているようです。
秘密にされると暴きたくなるのが人間の性、Capra君はどうしても秘密の値を知りたいです。
果たしてどうすれば秘密の値を手に入れられるでしょうか?

public class HasSecret
{
    private float ????? = ?????;
    public float dummy1 = 3.14f;
    private int dummy2 = 314;
}

ならディスアセンブルすれば…? 見出しへのリンク

確かに、dnSpyというC#で凄まじい威力を発揮するディスアセンブラを使えば瞬殺できます。ただ、やはりC#のコードで解決したいですよね。(というより、dnSpy使うのなら記事の意味がなくなってしまいそう…)

GitHub - 0xd4d/dnSpy

ということでC#のコードからHasSecret内の変数にアクセスしてみましょう。

まずは愚直にアクセス 見出しへのリンク

試しに、dummy2に何も考えずにアクセスしてみましょう。

var secret = new HasSecret();
Console.WriteLine(secert.dummy2);
error CS0122: 'HasSecret.dummy2' is inaccessible due to its protection level

当然ながらエラーが発生しました。これでアクセス出来たらライブラリ開発者はたまったものじゃないですよね。せっかくの実装の隠蔽が意味をなさなくなりますから。

また、この方法だと、privateな変数にアクセスするどころか変数名さえわかりません。
今回は変数名さえわからない状態なので、どうすればいいのでしょうか…

ここでSystem.Reflection 見出しへのリンク

ここから、System.Reflectionを使っていきます。
System.Reflectionは型の情報を使用して動的に物事を処理する方法を提供するクラスを揃えたバリューパックのような名前空間です。

それではそれらを使用して、privateな変数にアクセスしていきましょう。
まず、GetType関数を使って動的に型を取得します。静的に型を取得したい方はtypeofキーワードを利用するといいと思います。
受け取った型の型はSystem.Typeとなります。

// using System.Reflection;

var secret = new HasSecret();

var type = secret.GetType();

型を変数として受け取ったら、型が持っているGetFieldsを呼び出します。名前から想像がつくように、型から、条件を満たす全てのFieldの情報を取得してくる関数です。因みに戻り値はSystem.Reflection.FieldInfo[]です。

情報が取得出来たら、foreachで全てConsoleに表示してみましょう。

var members = type.GetFields(
    BindingFlags.Instance |
    BindingFlags.Public |
    BindingFlags.NonPublic
);

foreach(var member in members)
{
    Console.WriteLine(member.Name);
}
$ dotnet run
password
dummy1
dummy2

HasSecretに含まれる全てのFieldの名前を表示できました。
その出力の中には、passwordという怪しげな変数があります。次はこの変数の値を読んでみましょう。

値を手に入れる 見出しへのリンク

秘密の値を保持している変数の名前がpasswordだとわかりました。
ただ、変数の名前がわかってもその値は分かりません。

とりあえず、先ほどGetFieldsを使用して手に入れた情報から、WhereFirstOrDefaultを使って、名前がpasswordであるFieldのFieldInfoを入手します。

続けて、GetValueを呼び出します。これは、FieldInfoの情報を基に、インスタンスから変数の値を取得する関数です。戻り値はObject型であることに注意してください。

最後に、floatへCastしてConsoleに表示してみます。

// using System.Linq;

var passwordVal = members // FieldInfo[]
    .Where(info => info.Name == "password") // IEnumerable<FieldInfo>
    .FirstOrDefault() // FieldInfo
    .GetValue(secret); // Object

Console.WriteLine((float)passwordVal);

(筆者注 : GetValueの戻り値は厳密にはobject?で、Null許容型です。いずれ紹介します。)

$ dotnet run
2.71828

ネイピア数らしき値が出ました。これがpasswordの値です。これで、HasSecretの全容が明らかになって目標を達成することができました!

public class HasSecret
{
    private float password = 2.71828f;
    public float dummy1 = 3.14f;
    private int dummy2 = 314;
}

ついでにprivateの変数の値を変更する 見出しへのリンク

ただ、答えがネイピア数であることが気に入らなかったCapra君はpasswordを円周率に変えたいと考えました。

これも、先程と似ている操作で行うことができます。
値を取得した時と同じ方法で名前がpasswordの変数のFieldInfoを手に入れて、SetValueという関数を呼ぶだけです。詳しい説明は不要でしょう。

members
    .Where(info => info.Name == "password")
    .FirstOrDefault()
    .SetValue(secret, 3.1415f);

さいごに 見出しへのリンク

System.Reflectionを多用するのは一部の場面を除いて、邪道でパフォーマンスはやはり直接アクセスに劣ります。(Delegateを使用した高速化や、GetValueDirectの利用とかである程度早くはなりますが。)
どうしてもprivateなメンバーにアクセスしたいときや、ソースコードを紛失したライブラリの解析などにぜひ役立ててみてください。(その場合、dnSpyの方が圧倒的に便利)