前言
当我们调试程序时,经常需要在监视窗口中查看对象的值。默认情况下,我们看到的是 ToString() 方法返回的内容:.
var user = new User { Id = 1, Name = "MyIO" };
我们可以通过重写 ToString() 方法,以便看到一些更有意义的值:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public override string ToString()
{
return $"{this.Id} {this.Name}";
}
}
但是,我们发现,对于List<T>
,监视窗口中显示的却不是 ToString() 方法返回的内容,而是特殊值(当前列表项数量):
var list = new List<User>() { new User { Id = 1 }, new User { Id = 2 } };
这是怎么做到的呢?
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}";
}
}
高级用法
对于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 中谨慎使用方法。