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