One piece of feedback I heard in the MVP sessions this week is that debugging deep class hierarchies in C# is painful. By default C# will only display the fields and properties declared on a given type. To get to base class members you must expand the base node. For large hierarchies this can take several rounds of expansions to get to the desired value.
Take for instance the following class hierarchy
class Animal { public string name; public Animal(string name) { this.name = name; } } class Dog : Animal { public string color; public Dog(string name, string color) : base(name) { this.color = color; } } class Mutt : Dog { public List<string> breeds; public Mutt(string name, string color, params string[] breeds) : base(name, color) { this.breeds = new List<string>(breeds); } }
When you are working with an instance of Mutt in the debugger, it takes 3 rounds of clicking to see what it’s name is.
The bigger hierarchy the more clicks that are needed to get the the value that is desired. This can lead to a bit of frustration when you have significantly deep object hierarchies.
Several MVP’s asked for an option to flatten the hierarchies in a debugging session so they could get at their data quicker. The bad news is this option does not exist today and will not be present in Visual Studio 2010. The good news though is that you can still get this behavior in Visual Studio today by taking advantage of the existing debugging infrastructure.
In Visual Studio 2005 the debugging team added a set of attributes to the BCL which allowed end users to customize their debugging experience. We can use three of these attributes to create a flattened hierarchy for a given object.
The basic strategy for flattening a hierarchy is to do the following in our type proxy.
The result is a type proxy which will display all of the members of an object inline. Here is the full code sample
internal sealed class FlattenHierarchyProxy { [DebuggerDisplay("{Value}", Name = "{Name,nq}", Type = "{Type.ToString(),nq}")] internal struct Member { internal string Name; internal object Value; internal Type Type; internal Member(string name, object value, Type type) { Name = name; Value = value; Type = type; } } [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly object _target; [DebuggerBrowsable(DebuggerBrowsableState.Never)] private Member[] _memberList; [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] internal Member[] Items { get { if (_memberList == null) { _memberList = BuildMemberList().ToArray(); } return _memberList; } } public FlattenHierarchyProxy(object target) { _target = target; } private List<Member> BuildMemberList() { var list = new List<Member>(); if ( _target == null ) { return list; } var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; var type = _target.GetType(); foreach (var field in type.GetFields(flags)) { var value = field.GetValue(_target); list.Add(new Member(field.Name, value, field.FieldType)); } foreach (var prop in type.GetProperties(flags)) { object value = null; try { value = prop.GetValue(_target, null); } catch (Exception ex) { value = ex; } list.Add(new Member(prop.Name, value, prop.PropertyType)); } return list; } }
The last step is to attribute the root of our type hierarchy with this type proxy.
[DebuggerTypeProxy(typeof(FlattenHierarchyProxy))] class Animal {
Now when when debugging instances which derive from Animal developers will see a flattened hierarchy of values.