表題のようなことを思ったのでググったらありました(おわり)。

というだけでは味気ないのでもう少しDumpについて考えていきましょう。

実際のところDumpの需要として「階層になっている変数の情報を取得したい」というものがあると思います。例えばPHPのvar_dumpですね。ついでに階層が見やすく表示できると嬉しいです。

先程のブログ記事の方法では、コレクションやクラスがプロパティやフィールドとしてあると、そのコレクションやクラスのクラス名を表示するだけになってしまいます。階層を掘っていくこともできないのでまだちょっと物足りないですね。

そもそもC#ではデバッガーを使えば実行時にリアルタイムでこの辺の情報見られるんですが(ブレークポイントぽちぽち差し込んで)、それでも文字列にすると便利ということもあるでしょう。

というわけでvar_dumpライクなことをやってみましょう。

クラスそのものの情報を扱うので、もちろんリフレクションする必要があります。つまりリフレクションが登場した時点でDumpって動的型付け言語における要件ではないんですね。なので標準ではそんな機能存在していません。

ちなみに皆さんはリフレクションしてますか? しなくていいです。むしろしないほうがいい。設計の意味がなくなってしまうので、デバッグ用途か相当に限られた範囲でのみ使うようにしましょう。
余談ですが、リフレクションを使えばUnityの場合一般に公開されていないEditorの機能をハックして使うことができます。なぜかAsset Storeにはそういうコードがたくさんあって、しかもかなり有名なAssetですら平然と行っています(Very Animationとかすごいです)。審査に通っているのでこういうことも許されているのでしょうか……?

余談が過ぎましたが、じゃあvar_dumpしていきましょう。これちょっと嫌なのは遅延評価を含むプロパティがある時ですね。うーん、どうしよう。

とか言ってたらPHPのvar_dumpも同じような問題で死ぬことを教えてもらったのでこれはもう仕方のない問題のようです。無限ループや遅延評価は考えないことにしましょう!

PHPもvar_dumpすると場合によっては無限ループとかで死ぬ場合がある


追記:2018/12/31
この記事見た人から情報もらいました。

PHP の var_dump は再帰を RECURSION と示すだけで死ぬことはない


だそうです。あと最近はIDEが賢いのでvar_dumpを使わないことも多いとか。素敵な開発環境!

あった

と、あれこれしつつ調べてみたら、要件を満たすライブラリを見つけてしまいました。これですよこれ。というわけで実装はやめて、このコードを軽く覗いてみましょう。

まず使い方から。めちゃくちゃ簡単です。ObjectDumper.Dump(val) とするだけ。
試しに書いてみたコードがこちら。

結果はこんな感じ。

良い感じ!

さて、じゃあ中身覗いてみましょう。読むべきコードはこれ

DateTimeに特殊化してあったり、列挙型も取れるようになっていたり、KeyValuePair<T,U>な型まで対応しています。

具体的な流れとしては再帰的に構造をたどっていく感じですね。たどる最大の深さも決められます(IsMaxLevel)。

FormatValueで変数の型をチェックし、特定の型(プリミティブ型、DateTime型、Enumの派生型)だったら表示します(再帰の終了条件)。

Dictionary(っぽい型)の場合KeyとValueに分けて再度FormatValue。列挙型の場合各要素に対し、FormatValue。それ以外ならそのオブジェクトの要素についてFormatValueを行います。

ちなみに、型情報取得のために呼び出しているGetRuntimePropertiesは、GetPropertiesのちょっとしたラッパーです。なんかややこしいことしているのかと思ったらそんな事ありませんでした。

抜き出してみるとこんな感じ。バリデーションを行い、通ったらeverythingなるフラグでGetPropertiesしているだけですね。へー。


private const BindingFlags everything = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
private static void CheckAndThrow(Type t)
{
if (t == null) throw new ArgumentNullException("type");
if (!(t is RuntimeType)) throw new ArgumentException(Environment.GetResourceString("Argument_MustBeRuntimeType"));
}
public static IEnumerable<PropertyInfo> GetRuntimeProperties(this Type type)
{
CheckAndThrow(type);
return type.GetProperties(everything);
}

まとめ

なるほど、ObjectDumper.NET。良いですね。nugetにもあるので使ってみましょう。
ObjectDumperとObjectDumper.NETがありますが、今回紹介したのは後者です。前者はメンテナンスされていない雰囲気を感じたのと.NET Coreだとビルド通らなさそうな雰囲気を感じたので後者を使いました。

ではまた。