DebuggerDisplay 属性もいいけど DebuggerTypeProxy 属性もね

前回紹介した DebuggerDisplay 属性ですが、所属的には System.Diagnostic 名前空間 のクラスになります。Debug クラスとか Stopwatch クラスとかそこらへんで割と頻繁にお世話になっててよく見る名前空間ですね。で、 DebuggerDisplay 属性がよかったので他にも何かいいのないかなーと眺めてたら面白そうなのありました。 DebuggerTypeProxy 属性です。

DebuggerTypeProxyAttribute クラス (System.Diagnostics)
DebuggerTypeProxy 属性の使用

もう Proxy とか付いてる時点で何か面白そうな感じがするので調べてみました。

目的と簡単な使用例

DebuggerTypeProxy 属性は、適用した型をデバッグ時に表示する際にその代わりとして表示される型を指定するものです。

DebuggerDisplay 属性にはちょっとしたプロパティやメソッド呼び出しを記述できた訳ですが、その指定は文字列リテラルです。なので、メンバが多いクラスの表示だったり、式だったりを指定しようとすると割と面倒です。

internal class Program
{
    private static void Main(string[] args)
    {
        var obj = new Employee {Id = 1, Name = "太郎", Department = "開発部", Title = "課長", MonthlySalary = 300000};
        Console.WriteLine(obj);
    }
}
// メンドイ!
[DebuggerDisplay(
    "社員 ID : {Id}, 名前 : {Name}, 部署 : {Department}, 役職 : {Title}, 給与 : {MonthlySalary}, 年俸 : {MonthlySalary*12}")]
public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Department { get; set; }
    public string Title { get; set; }
    public int MonthlySalary { get; set; }
}

なんか、、、例が悪くて課題感が伝わりにくいかもしれませんがそこはかとなく漂う残念さは伝わるかと。ToString() 使えって?ご尤も。というか ToStrgin() 自由に実装していいんだったら DebuggerDisplay 属性だっていらないんですよ!使いたくても使えないことがあるんですよ!デバッグ用じゃないってうるさい人がいるんですよ(正しいけど) (ノД`)

で、こういう時に使えるのが DebuggerTypeProxy 属性です。

internal class Program
{
    private static void Main(string[] args)
    {
        var obj = new Employee {Id = 1, Name = "太郎", Department = "開発部", Title = "課長", MonthlySalary = 300000};
        Console.WriteLine(obj);
    }
}

[DebuggerTypeProxy(typeof (EmployeeDebugView))]
public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Department { get; set; }
    public string Title { get; set; }
    public int MonthlySalary { get; set; }
}

public class EmployeeDebugView
{
    private readonly Employee _employee;

    public string Id { get { return "Id : " + _employee.Id; } }

    // 他のプロパティは省略

    // プロキシされるクラスを引数に受けるコンストラクタが必要
    public EmployeeDebugView(Employee e)
    {
        _employee = e;
    }
}

これをデバッグ実行するとどうなるか。

f:id:kendik:20150417012408p:plain

わあすごい!これは便利、、、なのか、、、?

DebuggerTypeProxy の制約

DebuggerTypeProxy 属性は、ToString() をオーバーライドしてそれが表示されれば便利だったんですけどね。表示されないんです。

public class EmployeeDebugView
{
    private readonly Employee _employee;

    public string Id { get { return "Id : " + _employee.Id; } }

    // 他のプロパティは省略

    public EmployeeDebugView(Employee e)
    {
        // ガード節省略
        _employee = e;
    }
    // 追加
    public override string ToString()
    {
        return "自由に書けるよ!やったねたえちゃん!";
    }
}

プロキシクラスをこう書き換えてもダメです。表示されません。
それと、 DebuggerDisplay 属性みたいにプライベートメンバが覗けたりもしません。意味あるのかという意見は置いておいて、こう書いてもダメです。

internal class Program
{
    private static void Main(string[] args)
    {
        var obj = new Employee();
        Console.WriteLine(obj);
    }
}

[DebuggerTypeProxy(typeof (EmployeeDebugView))]
public class Employee
{
    private string _field = "private field";
}

[DebuggerDisplay("{_employee._field}")]
public class EmployeeDebugView
{
    private readonly Employee _employee;

    public EmployeeDebugView(Employee e)
    {
        _employee = e;
    }
}

なので MSDN ではプロキシクラスは対象クラスのインナークラスとして定義することを推奨していますね。

使いどころ

うーん。。。

DebuggerDisplay 属性も限定的でしたが、 DebuggerTypeProxy 属性も相当に限定的ですね。ただ、デバッグ表示したいメンバって大抵一つ二つじゃなくてたくさんあることの方が多いと思います。 DebuggerDisplay を指定するとデバッグ表示全体を上書きされてしまうので、自然、文字列でぺたぺたプロパティ名書いてく必要がありそうですが、そういう時に DebuggerTypeProxy 属性は便利ですね。リファクタリングにも対応しますし。案外と実務では DebuggerDisplay 属性より使いどころはあるかもしれません。でもプロパティ書くのが面倒なんだよなぁ、、、ToString() 表示してくれればなぁ。

プロパティで全部表示しちゃえ

もう全部シリアライズしたったらええねん。

internal class Program
{
    private static void Main(string[] args)
    {
        var obj = new Employee { Id = 1, Name = "太郎", Department = "開発部", Title = "課長", MonthlySalary = 300000 };
        Console.WriteLine(obj);
    }
}

[DebuggerTypeProxy(typeof(EmployeeDebugView))]
public class Employee
{
    private readonly string _field = "private field";
    public int Id { get; set; }
    public string Name { get; set; }
    public string Department { get; set; }
    public string Title { get; set; }
    public int MonthlySalary { get; set; }
}

public class EmployeeDebugView
{
    private readonly Employee _employee;

    public string Serialized
    {
        get
        {
            var resolver = new DefaultContractResolver();
            resolver.DefaultMembersSearchFlags |= BindingFlags.NonPublic;
            var setting = new JsonSerializerSettings {ContractResolver = resolver};
            return JsonConvert.SerializeObject(_employee, setting);
        }
    }

    // プロキシされるクラスを引数に受けるコンストラクタが必要
    public EmployeeDebugView(Employee e)
    {
        _employee = e;
    }
}

f:id:kendik:20150417012434p:plain