Oct 092012
 

Introduction

BaseMemberPromotingProxy is a (terribly named) class meant to be used as the target for the DebuggerTypeProxy attribute. The code is based off of Jared Parsons’ FlattenHierarchyProxy. Like the original, BaseMemberPromotingProxy (which will be referred to as BMPP throughout the rest of this post) is designed to improve the debugger displays of classes that inherit from other classes.
 

What the what?

The best way to demonstrate BMPP is by giving an example. Consider these classes:

public class Dessert {
    public int Calories { get; set; }
    public Dessert(int calories) {
        Calories = calories;
    }
}

public class Pastry : Dessert {
    public bool Flaky { get; set; }
    public Pastry(bool flaky, int calories) : base(calories) {
        Flaky = flaky;
    }
}

public class Cake : Pastry {
    private String _baker;
    public Cake(string baker, int calories) : base(false, calories) {
        _baker = baker;
    }
}

Let’s create an instance of Cake and look at the debugger display that Visual Studio provides by default.

var cake = new Cake("Jerry's bakery", 250);

We set a breakpoint after the instance of Cake is created, and Visual Studio pauses execution. Hovering over cake, we see:
 

 
So where are the members of Pastry and Dessert? We need to drilldown further to find them. It takes another click to see the Flaky property of Pastry, and one more additional click to see Calories of Dessert (as pictured below).
 

 
This is very inconvenient; even more so when actual complicated class structures are involved. Enter Jared Parsons’ FlattenHierarchyProxy.
 

FlattenHierarchyProxy

Let’s see what FlattenHierarchyProxy (off of which BMPP is based) can do for us. We apply it to Cake with a DebuggerTypeProxy attribute.

[DebuggerTypeProxy(typeof(FlattenHierarchyProxy))]
public class Cake : Pastry {
    //...

Now, when we mouse over the cake variable, we get:
 

 
Jared’s type proxy greatly improves the debugger display by displaying all members (from both parent and subclasses) “side-by-side”. I’ve decided to call this the “promotion” of the members of subclasses (hence the name of my class). So what does BMPP provide?

 

BaseMemberPromotingProxy

I was really happy with FlattenHierarchyProxy, but felt the need to make a few improvements. BMPP contains the following improvements and new features:

 

“Opt-in” mode for properties and fields

Sometimes you don’t want to promote every member of the subclass(es) that your class derives from. Maybe a subclass contains a property or field that isn’t relevant to your parent class, so you’d rather not see it in the debugger display. Going back to our Cake example, let’s say that we don’t care about the Flaky property. We could apply a DebuggerBrowsable attribute to hide it (see below), but then we wouldn’t see Flaky even if we were debugging a plain instance of Pastry.
 
The solution is to use one of BMPP’s spiffy new attributes:

  • PromoteProperty(/* name of property */): Promote the specified property
  • PromoteField(/* name of field */): Promote the specified field

 
To hide Flaky while debugging instances of cake, we opt-in just Calories by applying a PromoteProperty attribute:

[DebuggerTypeProxy(typeof(BaseMemberPromotingProxy))]
[PromoteProperty("Calories")]
public class Cake : Pastry {
    //...

Now we get:
 

 
The way this works is that the use of a PromoteProperty attribute switches BMPP to “opt-in” mode for properties. Normally, BMPP grabs whatever properties it can from the parent class and all subclasses. Applying a PromoteProperty attribute turns off this behavior: only properties that are opted-in with PromoteProperty attributes are promoted. This behavior is the same for PromoteField properties. However, PromoteProperty does not switch on opt-in mode for fields, or vice-versa.
 
Also, note that we didn’t have to explicitly opt-in the _baker field since it was declared in the parent class, not a subclass. Members of the parent class are always included. This behavior exists with properties as well.
 
Note: I have considered implementing an opt-out mode in addition to opt-in mode. Please comment on my blog or submit an issue on BitBucket if you are interested in this for the next version.

 

Alphabetical ordering of members

This is something that Visual Studio does automatically, however since we are constructing a custom debugger display, we need to perform the sort ourselves. Replacing the last line of BuildMemberList() with the following does the trick:

return list.OrderBy(m => m.Name).ToList();

Note that the collective list of fields + members is sorted as one list. If you want different behavior (for example, you might want a sorted list of fields to appear before a sorted list of properties), let me know and I can add the functionality.

 

Honoring of DebuggerBrowsable attributes

Let’s rewrite the declaration of the _baker field from the Cake class as:

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private String _baker;

FlattenHierarchyProxy would still display the _baker field, even though we’ve chosen to hide it using the DebuggerBrowsable attribute. The fix that BMPP makes is to introduce this internal helper function:

private static bool IsMemberBrowsable(MemberInfo memberInfo) {
    return memberInfo.GetCustomAttributes(typeof(DebuggerBrowsableAttribute), true)
           .Select(p => (DebuggerBrowsableAttribute)p)
           .All(p => p.State != DebuggerBrowsableState.Never);
}

Now, when BMPP builds the property and field lists, it uses the IsMemberBrowsable function as a filter.

 

Get it

The class is hosted on BitBucket. Documentation will be up on BitBucket soon – for now, use this page as a reference.
 

Performance information

I have not done extensive testing on the performance of BMPP. It is noticeably slower than FlattenHierarchyProxy, however. The performance hit comes from the fact that reading attributes is a fairly expensive reflection task, relative to everything else that the proxy does. If you have any ideas on how to improve performance, please let me know.

Leave a Reply

%d bloggers like this: