Customing Dump Output

You can customize how objects are dumped by writing a single method called ToDump. You can write this method either on the type itself or in LINQPad's My Extensions query (so you don't need to make any changes to the type whose output you wish to customize). ToDump also simple to write: it's just two lines of code to make all boolean values display in a different color, for instance.

It's also possible to write a custom visualizer, which means writing a WPF or Windows Forms control to display an object. The consumer typically invokes a custom visualizer by calling an extension method that you write (the normal Dump may still be useful for exploring properties). WPF/WinForms is good in allowing for complexity and interactivity (for example, displaying a graph), but it relies on an event loop, so the visualization does not appear until query's main thread has completed executing, and it displays in a separate panel rather than inline.

ToDump

We'll use the following class for our examples:

public class Customer
{ 
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public DateTime BirthDate { get; set; }
}

Suppose you only want to see FirstName and LastName when dumping instances of Customer. If you own the source code for Customer and want to bake LINQPad-friendliness into the class itself, the solution now is to define a method called ToDump on the class as follows:

public class Customer
{ 
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public DateTime BirthDate { get; set; }

   // The ToDump method can be private, so it doesn't pollute your type's public interface.
   object ToDump() => new { FirstName, LastName };
}

LINQPad will notice that your class has a parameterless method called "ToDump" and call it whenever dumping instances of that type.

But what if you don't own the Customer class, and cannot modify the source code to add the ToDump method? The solution is go to LINQPad's My Extensions query, and write a static version of ToDump as follows:

void Main()
{
   // You can test your ToDump here by dumping a Customer object.
   // Notice that we just Dump it (LINQPad finds the ToDump method itself). 
   new Customer { FirstName = "Joe", LastName = "Bloggs" }.Dump();
}

public static class MyExtensions
{ 
}

// Define a top-level method called ToDump:
static object ToDump (object input)
{
   var customer = input as Customer

   if (customer != null) 
      return new { customer.FirstName, customer.LastName };
   
   return input;
}
From LINQPad 7.3, you can define a static ToDump method in any query, not just in My Extensions. This is handy when you want to customize Dump for just one query. ToDump also works in queries that you #load.

The ToDump returns whatever you want to dump, and it can be anything you like. In our example, we returned an anonymous type, which is the easiest way to specify a list of properties or fields. Of course, you can run functions on the output if you choose:

   if (customer != null)
      return new { LastName = LastName.ToUpper(), FirstName };

Or if you'd prefer to see a simple string when dumping a customer:

   if (customer != null)
      return customer.FirstName + " " + customer.LastName;

You can even return a WPF/WinForms control (a custom 'visualizer') which LINQPad will render. However, bear in mind that it will not start rendering until the query has completed, and it will show in a separate pane rather than in-line.

Keeping 'My Extensions' Clean of References

In the preceding three examples, we would need to an assembly reference in My Extensions, to whatever library contains the Customer type. This can be undesirable because references that you add to My Extensions end up in every query. If the Customer assembly is specialized and not of general use to your queries, you'll want to keep it away from My Extensions. You can accomplish this through dynamic binding:

static object ToDump (object input)
{
   // Now we don't need to add a reference to the assembly containing the Customer type:

   dynamic dyn = input;

   if (input.GetType().FullName == "MyApplication.Customer") 
      return new { dyn.FirstName, dyn.LastName };
   
   return input;
}

From LINQPad 7.3, another solution is to define the static ToDump method in a query that you #load. You can then "plug in" that behavior as required.

More Flexibility With ToExpando

Sometimes, you'll want to specify the properties to dump at run-time rather than compile-time. Or you might even want to build an 'object' at runtime from scratch. The solution is to return a System.Dynamic.ExpandoObject. LINQPad displays expandos as though they were real-life static objects. To demonstrate, create a new C# Statements query and execute this:

IDictionary<string,object> custom = new System.Dynamic.ExpandoObject();
custom["FirstName"] = "Joe";
custom["LastName"] = "Bloggs";
custom.Dump();

We end up with the same output as if we had written a Customer class with FirstName and LastName properties.

Leveraging this, we could re-write our very first example as follows:

public class Customer
{ 
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public DateTime BirthDate { get; set; }

   object ToDump()
   {
      IDictionary<string,object> custom = new System.Dynamic.ExpandoObject();
      custom["FirstName"] = FirstName;
      custom["LastName"] = LastName;
      return custom;
   }
}

We can make this more useful by coupling it with reflection. The following ToDump method includes all properties except those of DateTime type:

   object ToDump()
   {
       IDictionary<string,object> custom = new System.Dynamic.ExpandoObject();

       var props = GetType().GetProperties (BindingFlags.Public | BindingFlags.Instance)
           .Where (p => p.PropertyType != typeof(DateTime));

       foreach (var prop in props)
          try
          {
             custom [prop.Name] = prop.GetValue (this);
          }
          catch (Exception ex)
          {
             custom [prop.Name] = ex.GetBaseException();  // Report error in output 
          }			 

       return custom;
   }

This kind of approach is useful when applied to an abstract base class, whose subclasses may contain properties that you wish to exclude (or make lazy), based on their type.

Util.ToExpando

LINQPad's Util.ToExpando method takes any object and converts it into an ExpandoObject that you can customize and dump. You can also specify which properties/fields to include/exclude (in a comma-separated string) or give it a list of MemberInfo objects to include. We can rewrite the preceding example more simply with ToExpando, as follows:

object ToDump()
  => Util.ToExpando (this, GetType().GetProperties().Where (p => p.PropertyType != typeof(DateTime)));

Here's an example of using a comma-separated list to exclude the fields called BirthDate and SomethingElse:

object ToDump() => Util.ToExpando (this, exclude:"BirthDate,SomethingElse");

You can also specify nameOrder, which instructs LINQPad to order the fields/property alphabetically. Comma-separated lists can include parameterless methods as well as fields and properties.

If you're writing the ToDump method inside the type to be customized (instead of in My Extensions), you won't want to take a dependency on LINQPad.exe in order to access the Util.ToDump method. The solution is to paste the source code for ToExpando into your application (here it is).

Lazy Properties

Sometimes, a property is expensive to evaluate, but still potentially useful to see. Rather than excluding it, a better option is to expose a hyperlink that evaluates the property when clicked. To do this, use System.Lazy<T>:

public class Customer
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public DateTime BirthDate { get; set; }

   object ToDump() => new
   {
      FirstName,
      LastName, 
      BirthDate = new Lazy<DateTime> (() => BirthDate)
   };
}

The Util.OnDemand method accomplishes the same thing as System.Lazy<T>, but also lets you specify what text to display. However, because Util.OnDemand is part of LINQPad.exe, it makes sense to use it only from the static ToDump in My Extensions.

Forcing Object Expansion

LINQPad calls ToString on objects that implement System.IFormattable, displaying them as plain text rather than expanding them (an example is DateTime). There's a simple shortcut to force such a type to be expanded; just write a ToDump method that returns the object:

object ToDump() => this;

Or, if you're customizing via the static ToDump method in My Extensions, call Util.ToExpando on the object:

static object ToDump (object input)
{
   if (input is DateTime) return Util.ToExpando (input);
   return input;
}

This also works with enumerable objects that have a payload; it forces LINQPad to expand the properties rather than enumerating the collection.

Emitting Images

To emit an image, return a System.Drawing.Image:

object ToDump()
{
   var bitmap = new System.Drawing.Bitmap (300, 300);
   using (var graphics = System.Drawing.Graphics.FromImage (bitmap))
   {
      graphics.Clear (System.Drawing.Color.Navy);
      graphics.DrawEllipse (System.Drawing.Pens.White, 0, 0, bitmap.Width, bitmap.Height);
   }
   return bitmap;
}

Customizing Dump for Simple Types

Your static ToDump method in My Extensions can work for customizing the way LINQPad displays simple types, too. For example, the following colors the boolean values True and False in teal and brown:

static object ToDump (object input)
{
   if (input is bool && (bool)input) return Util.WithStyle ("True", "color:teal");
   if (input is bool && !(bool)input) return Util.WithStyle ("False", "color:brown");
}

Custom HTML

LINQPad's Dump method (in the normal rich-text mode) works by generating HTML. You can take advantage of this by emitting the HTML yourself and implement the most extreme form of customization. If you're customizing from within the static ToDump method in My Extensions, call Util.RawHtml to emit the desired HTML. If you're writing a ToDump method within the type itself, you can emit raw HTML by wrapping the HTML in an XElement with the name "LINQPad.HTML". For example:

   object ToDump() => new XElement ("LINQPad.HTML",
      new XElement ("div", new XAttribute ("style", "color:violet; font-size:150%"),
         FirstName + " " + LastName));

This will result in a Customer being displayed as follows:

Joe Bloggs