Home

Objects displayed only as strings, not in grid/expandable

I am new to LINQPad so this may be obvious - but I could not find a reason or an explanation.
Basically, some objects that are results of my queries are displayed in an expandable (grid) form, with each property displayed separately, and possibly further inspectable.

However, some other objects that I have, do not display in this way. Instead, I simply get a textual representation, which appears to be their ToString() result. But that's not the way I want, and furthermore, I cannot tell *why* certain objects behave this way, why others don't.

Thank you

Comments

  • edited June 2016
    I've answered the question on StackOverflow:
    http://stackoverflow.com/a/37935406/46223
  • Thank you. First of all, let me tell you that I have purchased LINQPad and it is a great product. So do not take anything I write as a criticism, rather like a suggestion for improvement,

    The solution proposed on SO isn't really good for my purpose. Let me explain my use case: I have a library for which I intended to provide a set of LINQPad examples/tutorials. The code in them should be as simple as possible.

    The problem with the proposed solution is that it complicates the code. Possibly not that much if the IFormattable object is the direct result of the query. But that's not typically the case: They can appear anywhere in the result, as sub-properties of properties of objects. Even if I could foresee where they appear, I would have to write a complicated query just to transform all objects in the result to Expanded. Doing so would defeat the purpose of writing the examples.

    I thing I guess why you have implemented like that: Some types are best displayed in that way, and it so happened that the types that Microsoft ships have IFormattable implemented in roughly such situations where non-expanded view is the best one. But assuming that the same applies to other, custom types is, in my view, wrong. For example, in my case, I can have IFormattable implemented on an object that carries some information about an event that has occurred in an industrial system. The event contains many different pieces of information, some of them can be quite complex and structured, but it implemented IFormattable so that the developer can easily display it in various forms, without having to write a lot of code for it. When using LINQPad on such objects e.g. for debugging, the developer would want to be able see all the details, without having to guess upfront which objects need to be converted to Expanded.

    I would like to propose several possibilities how to resolve it, in some future LINQPad version:
    1. Have an option to turn off the detection of IFormattable. That would be the simplest, but probably won't wok that well.
    2. Add a very small "expand" icon, something that won't visually do any harm, to types that currently show unexpanded (= IFormattable-s). After clicking on it, they would expand as other types do.
    3. Test for a dedicated attribute on a type, e.g. [LinqPadExpand(Boolean)]. When absent, LINQPad would behave as now. When present, it would interpret the true/false value of the attribute as an instruction whether to expand the object or not. In order to prevent a dependency on some LINQPad assembly, the test can be made simply for the attribute name (e.g. "LinqPadExpand"), and not its fully qualified type.

    My situation is now that I actually planned to implement IFormattable on more and more objects - now I need to think it over, but that would further and further decrease the usability of my library with LINQPad. Do you see the problem?

    I would appreciate if you let me know your thoughts.

    Best regards
  • edited June 2016
    Have you considered implementing ICustomMemberProvider on those types?

    http://www.linqpad.net/faq.aspx#extensibility

    You don't need to take a dependency on LINQPad.exe; you can copy and paste the interface definition into your assembly and LINQPad will "duck-type" the interface.
  • I see. Yes, this can probably be a solution - need to try it out first. It still seems a bit lengthy to write, but acceptable.

    Note that your implementation of Expanded on SO has a bug: It includes static properties, which is not what you normally want.

    Thank you, and best regards
  • I have now attempted to actually implement what you have suggested, and it does not work.

    I have a working implementation of ICustomMemberProvider on my class, but as soon as I also implement IFormattable on it, LINQPad stops calling ICustomMemberProvider members and displays a string instead.

    I am now in a situation which I cannot resolve - I need both IFormattable *and* a way to inspect my objects in LINQPad. :-(


  • You're right - ICustomMemberProvider should take precedence over IFormattable. I've fixed that for the next build.

    In terms of making this easier, a custom attribute is certainly an option, although discoverability could be an issue.

    Another option is to add a heuristic to make the default work in a greater number of scenarios. For instance, I could look at the property and field counts. If a type that implements IFormattable has more than 5 public properties and 5 private fields, it might be better to expand the object by default rather than calling ToString on it. What do you think?
  • Oh, thanks. Will test with the new build.

    I am lucky enough to have derived a large part of my object hierarchy from a common class. My expectation is that I can implement ICustomMemberProvider just once, using reflection on the "root" of the type hierarchy, and everything will work automatically then - if only ICustomMemberProvider takes precedence over IFormattable. If that works, it's good enough.

    The custom attribute would be nice; I do not like the heuristic approach that much, because it's not really predictable or intuitive.

    Best regards
  • I've uploaded the new build that fixes the ICustomMemberProvider bug.

    Am still working on a better solution.
  • edited July 2016
    I've just released a new beta with an experimental feature to simplify customizing Dump output.

    Instead of implementing ICustomMemberProvider, write a private or public method called ToDump which returns the data you wish to display. If you simply want to prevent ToString being called (as in your case), just do this:

    object ToDump() => this;

    To do the reverse, and force ToString():

    object ToDump() => ToString();

    To tell LINQPad to dump just a select set of properties, return an anonymous type:
    object ToDump() => new
    {
        ID,
        FirstName,
        LastName
    };
    You can make a column lazy so that it displays with a hyperlink as follows:
    object ToDump() => new
    {
        ID,
        FirstName,
        LastName,
        Orders = new Lazy<List<Order>> (() => Orders)    
    };
    If you need to determine the columns at runtime, use an ExpandoObject:
    object ToDump()
    {
        IDictionary<string, object> expando = new System.Dynamic.ExpandoObject();
    
        string someColumnName = "Column1";
        expando [someColumnName] = "foo";
        
        return expando;
    }
    The following dumps all properties except "Foo":
    object ToDump()
    {
        IDictionary<string, object> expando = new System.Dynamic.ExpandoObject();
    
        foreach (var prop in GetType().GetProperties (BindingFlags.Public | BindingFlags.Instance))
            if (prop.GetIndexParameters().Length == 0 && prop.Name != "Foo")
                try
                {
                    expando.Add (prop.Name, prop.GetValue (this));
                }
                catch (Exception ex)
                {
                    expando.Add (prop.Name, ex);
                }
    
        return expando;
    }
    
    Let me know how you get along.
  • That's pretty cool Joe :)
  • This is great Joe, thank you :) If I understand right, it can only be used if you have access to modify the source of the type/class you want to dump? So it doesn't help with my issue with a NameValueCollection as mentioned here? http://forum.linqpad.net/discussion/1067/dump-on-a-namevaluecollection
  • edited July 2016
    Could this behavior be enabled using extension methods?
    public static class MyExtensions
    {
    	// Write custom extension methods here. They will be available to all queries.
    	public static object ToDump(this NameValueCollection value) => value;
    }
    
    Would be a solution for kingkeith's request :)
Sign In or Register to comment.