Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Possible to accessing child "DebuggerDisplay" attribute of property?

Current state

Having two classes:

[DebuggerDisplay(@"One = {One}, two = {Two}")]
public class A
{
    public int One { get; set; }
    public B Two { get; set; }
}

[DebuggerDisplay(@"Three = {Three}")]
public class B
{
    public int Three { get; set; }
}

Using them:

var a = new A {One = 5, Two = new B {Three = 10}};

Inside the debugger, the tool tip value that is displayed at a is

One = 5, two = {DebuggerDisplayTest.B}

Goal

What I would want is something like

One = 5, two = 'Three = 10'

I know this could be achieved by overriding the ToString() method of class B. This just feels not right, since I'm writing code in my application for debugging only.

I also know that using a string similar to

[DebuggerDisplay(@"One = {One}, two = 'Three = {Two.Three}'")]

would work, too. This also does not feel right to me, since it would require that class A has knowledge of class B.

I would like to have more of a way to "inject" the value of DebuggerDisplay of type B to the instance of that type in class A.

Question

Is it somehow possible to access the DebuggerDisplay attribute of a member inside the DebuggerDisplay attribute of a "has-a" composited class?

Update

Probably, my requirement is not possible as per this SO answer. Maybe a good solution would be to override ToString in class B and do some if..else and use the Debugger.IsAttached property to behave different only inside the debugger.

Something like:

[DebuggerDisplay(@"Three = {Three}")]
public class B
{
    public int Three { get; set; }

    public override string ToString()
    {
        if (Debugger.IsAttached)
        {
            return string.Format(@"Three = {0}", Three);
        }
        else
        {
            return base.ToString();
        }
    }
}
like image 321
Uwe Keim Avatar asked Jun 25 '13 12:06

Uwe Keim


3 Answers

Piecing together a few things I've come up with this solution. It has the caveat it expects you to follow https://blogs.msdn.microsoft.com/jaredpar/2011/03/18/debuggerdisplay-attribute-best-practices/

[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class B
{
    public int Three { get; set; }

    private string DebuggerDisplay => $"Three = {Three}";
}

[DebuggerDisplay("{DebuggerDisplay,nq}")]
public class A
{
    public int One { get; set; }
    public B Two { get; set; }

    private string DebuggerDisplay => $"One = {One}, two = {Two.ReadDebuggerDisplay()}";
}

You'll need to make sure you have proper imports for where ever you stick this helper in relation to the code that needs to read child debugger displays.

public static class ReflectionHelper
{
    // https://stackoverflow.com/a/13650728/37055
    public static object ReadProperty(
        this object target,
        string propertyName)
    {
        var args = new[] {CSharpArgumentInfo.Create(0, null)};
        var binder = Binder.GetMember(0, propertyName, target.GetType(), args);
        var site = CallSite<Func<CallSite, object, object>>.Create(binder);
        return site.Target(site, target);
    }

    public static string ReadDebuggerDisplay(
        this object target, 
        string propertyName = "DebuggerDisplay")
    {
        string debuggerDisplay = null;
        try
        {
            var value = ReadProperty(target, propertyName) ?? "<null object>";

            debuggerDisplay = value as string ?? value.ToString();
        }
        catch (Exception)
        {
            // ignored
        }
        return debuggerDisplay ?? 
              $"<ReadDebuggerDisplay failed on {target.GetType()}[{propertyName}]>";
    }
}

I feel like this a pretty fair balance of purity and pragmatism for lowering the friction on achieving this. If you are less concerned about purity you could just make DebuggerDisplay public. I prefer that the ReadDebuggerDisplay operates in a "type-less" manner (avoids generic constraints and interfaces that would be needed to access DebuggerDisplay publicly).

like image 74
Chris Marisic Avatar answered Nov 05 '22 07:11

Chris Marisic


Copied possible solution from OP

Probably, my requirement is not possible as per this SO answer. Maybe a good solution would be to override ToString in class B and do some if..else and use the Debugger.IsAttached property to behave different only inside the debugger.

Something like:

[DebuggerDisplay(@"Three = {Three}")]
public class B
{
    public int Three { get; set; }

    public override string ToString()
    {
        if (Debugger.IsAttached)
        {
            return string.Format(@"Three = {0}", Three);
        }
        else
        {
            return base.ToString();
        }
    }
}
like image 38
2 revs Avatar answered Nov 05 '22 06:11

2 revs


[Disclaimer I'm affiliated with OzCode]

You can use OzCode's Reveal feature which supports nested debug information. Reveal in action!
The plus side is that you do not need to change your production code and once you define it for an instance it would be used automatically for all instances of that type.

like image 4
Dror Helper Avatar answered Nov 05 '22 05:11

Dror Helper