利用 DebuggerDisplay 特性定制监视窗口中变量显示方式

前言

当我们调试程序时,经常需要在监视窗口中查看对象的值。默认情况下,我们看到的是 ToString() 方法返回的内容:.

var user = new User { Id = 1, Name = "MyIO" };

利用 DebuggerDisplay 特性定制监视窗口中变量显示方式

我们可以通过重写 ToString() 方法,以便看到一些更有意义的值:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }

    public override string ToString()
    {
        return $"{this.Id} {this.Name}";
    }
}

利用 DebuggerDisplay 特性定制监视窗口中变量显示方式

但是,我们发现,对于List<T>,监视窗口中显示的却不是 ToString() 方法返回的内容,而是特殊值(当前列表项数量):

var list = new List<User>() { new User { Id = 1 }, new User { Id = 2 } };

利用 DebuggerDisplay 特性定制监视窗口中变量显示方式

这是怎么做到的呢?

DebuggerDisplay 特性

查看List<T>的源码,我们发现它使用了DebuggerDisplayAttribute,用来确定类或字段在调试器变量窗口中的显示方式:

[DebuggerDisplay("Count = {Count}")]
public class List<T> : IList<T>, IList, IReadOnlyList<T>

DebuggerDisplay 特性有一个参数,此参数是要在监视窗口的值列中为类型的实例显示的字符串。此字符串可以包含大括号({ 和 })。一对大括号之间的文本将作为字段、属性或方法进行计算。

如果同时使用,DebuggerDisplay 特性将替代 ToString() 方法显示在监视窗口的值列中:

[DebuggerDisplay("Id = {Id}")]
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }

    public override string ToString()
    {
        return $"{this.Id} {this.Name}";
    }
}

利用 DebuggerDisplay 特性定制监视窗口中变量显示方式

高级用法

对于List<T>这种不是由我们定义的类,我们既无法重写 ToString() 方法,也无法修改其 DebuggerDisplay 特性实现。

那么,这种情况又应该怎么处理呢?

查看DebuggerDisplayAttribute的源码,发现它允许被定义在AttributeTargets.Assembly级别,同时有Target属性可用于指定目标类型:

[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Delegate, AllowMultiple = true)]
public sealed class DebuggerDisplayAttribute : Attribute
{    
    //
    // 摘要:
    //     Gets or sets the type of the attribute's target.
    //
    // 返回结果:
    //     The attribute's target type.
    //
    // 异常:
    //   T:System.ArgumentNullException:
    //     System.Diagnostics.DebuggerDisplayAttribute.Target is set to null.
    public Type? Target { get; set; } 
}

因此,我们可以这样,进行全局定义:

[assembly:DebuggerDisplay("{DebuggerDisplayHelper.DebuggerDisplay(this),nq}", Target = typeof(List<User>))]

this指代当前被调试的实例,DebuggerDisplay是我们自定义的方法:

public static class DebuggerDisplayHelper
{
    public static string DebuggerDisplay(object obj)
    {
        if (obj is List<User> users)
        {
            return string.Join(",", users.Select(x => x.Id));
        }

        return obj.ToString();
    }
}

,nq后缀指示在显示最终值时不包含双引号(no quotes):

利用 DebuggerDisplay 特性定制监视窗口中变量显示方式

结论

通过上文,我们看到,利用 DebuggerDisplay 特性可以得到更好的调试体验。

但是,由于表达式会在调试时自动计算,可能导致性能问题,请在 DebuggerDisplay 中谨慎使用方法。