Stuff - mainly geek, a little diabetes, some rant. Possibly all three in the same post. Basically anything I find interesting and/or worth sharing.
These are my personal views, unless I've been hacked.
Ever wished you could watch the value of an out-of-scope variable while debugging in Visual Studio? You can, if you use the Make Object ID option in the watch/local window. Here's an example, with a variable p that goes out of scope when control jumps to a new method, or back to the calling method:
class Person
{
public Person(string name)
{
Name = name;
}
public string Name { get; set; }
}
public class Program
{
public static void Main(string[] args)
{
DoIt();
}
private static void DoIt()
{
var p = new Person("Baldrick");
DoIt2();
}
private static void DoIt2()
{
return;
}
}
If you step through this code in Visual Studio and add a watch to variable p in method DoIt, the watch window will look like this:
When you step into method DoIt2, or out to Main, and refresh the watch on p, you get this message in the watch window:
If you want to be able to reliably watch the variable, the solution is to Make Object ID. Let's take the code back to DoIt and right-click on the watch on variable p:
Click on Make Object ID, and you'll see variable p is allocated object ID "1#"
If you add a watch to 1#, this allows you to view the value of p even when p goes out of scope:
The 1# reference can be used to update the value of the underlying variable p:
which will be reflected when (if) the object comes back into scope:
What about Garbage Collection?
The Object ID doesn't create a managed reference to the underlying object, which means that it will not interfere with its garbage collection:
I did a quick introduction talk on Visual Studio Debugger Visualisers the other day, and one question I was asked was [something like] "Do you have to serialise the object to be visualised? Could you pass it to the visualiser and have it read the properties via reflection?"
The answer is yes and no. You do have to serialise the object to a stream, but you can implement your own method of serialisation. I'll try to explain that; the data to be visualised must pass between the process boundaries of the running process and the debugger process via a System.IO.Stream. The contents of this stream can be whatever you want, as long as the visualiser knows how to interpret it. Here's an example:
Note the second argument to the DebuggerVisualiser attribute - this is vital to ensure that the correct class is used to provide the object to be visualised.
As the object is not marked as serialisable, it can't be provided to the visualiser via the normal GetObject method of the IVisualObjectProvider (the GetObject method relies on serialisation). However, what you can do is create a custom implementation of VisualizerObjectSource, to be used by the objectProvider. VisualiserObjectSource has a virtual method, GetData, with two parameters. The first parameter is an object, which will be the object to be visualised. The second parameter is a stream, which needs to be populated with the visualisation data for the object:
In this example, I'm passing an integer which represents the Argb value of the object's colour.
When the Show method on the visualiser is called, it's passed an instance of IVisualizerObjectProvider. With XML serialisable objects you would normally obtain the object to be visualised via the objectProvider.GetObject method. When the object is not serialisable, you need to call the GetData() method instead; the objectProvider calls the GetData method of the VisualizerObjectSource, and returns the contents of its outgoingStream argument. This can then be used to visualise the object:
When this is all put together, the visualiser looks like this:
This post will show how to create a debugger visualiser for visual studio, and use it to display and update a debugged object's data. It is a step-by-step guide, and the resulting code (written in C#) can be found on GitHub. I recommend getting this code to get a feel for how to produce a visualiser.
The object that we're going to visualise has two properties: Colour and IntArray.
public class DemoObject
{
static Random _random = new Random();
public DemoObject()
{
Colour = Color.DarkOrange;
//Build a large array that isn't easy to see in the debugger
IntArray = InitialiseRandomArray(18, 7);
}
public int[,] IntArray { get; private set; }
public Color Colour { get; set; }
private static int[,] InitialiseRandomArray(int index1, int index2)
{
var intArray = new int[index1, index2];
foreach (var i in Enumerable.Range(0, index1))
{
foreach (var j in Enumerable.Range(0, index2))
{
intArray[i, j] = _random.Next(0, 255);
}
}
return intArray;
}
}
As you can see, this is not easily viewed in the debugger:
Our object's IntArray property is a two dimensional array, which we're going to display in a table; Colour will be displayed as the background colour of a picture box.
A debugger visualiser can be a windows control or form; for this demo we'll be using a form. So, create the form, give it a name of "DemoObjectVisualiserForm". Add a textbox - make it multiline, change its name to "arrayContents" - this will be used to display the tabulated contents of the 2 dimensional array. Add a picturebox, call it "colourBox" - this will show the object's colour, rather than the description of the colour shown in the debugger.
As this form is to display an object of type DemoObject, create a constructor that takes an instance of DemoObject as its argument; store this object in a private member variable "_objectToVisualise".
To expose the visualiser API, add a reference to Microsoft.VisualStudio.DebuggerVisualizers:
Add a public class DemoObjectVisualiser, inheriting from DialogDebuggerVisualizer. Implement its Show method:
public class DemoObjectVisualiser : DialogDebuggerVisualizer
{
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{
//make sure the object is the correct type
var objectToVisualise = objectProvider.GetObject() as DemoObject;
//Show the visualiser
var form = new DemoObjectVisualiserForm(objectToVisualise);
windowService.ShowDialog(form);
}
}
Show is the method that's called by the Visual Studio debugger, and takes 2 arguments: IDialogVisualizerService and IVisualizerObjectProvider. IDialogVisualizerService allows the visualiser to be displayed on the UI, and IVisualizerObjectProvider wraps the object to be visualised.
As the object is serialised to be passed between Visual Studio and the DialogDebuggerVisualizer, DemoObject must be decorated with the Serializable attribute unless you plan to implement manual serialisation and use a VisualizerObjectSource.
[Serializable]
public class DemoObject
In order to test your visualiser, you can use an instance of the VisualizerDevelopmentHost - this is a proxy of the Visual Studio debugger and is a quick and easy way to fire up your debugger visualiser without running your client code.
This example is a console application, with a reference to Microsoft.VisualStudio.DebuggerVisualizers
using Microsoft.VisualStudio.DebuggerVisualizers;
using VisualiserDemo;
namespace DevelopmentHostDemo
{
class Program
{
static void Main(string[] args)
{
var objectToVisualise = new DemoObject();
var host = new VisualizerDevelopmentHost(objectToVisualise, typeof(DemoObjectVisualiser));
host.ShowVisualizer();
}
}
}
So that the debugger knows to give the option of visualising an object using our new visualiser, the object to visualise needs to be decorated with the DebuggerVisualiser attribute:
[DebuggerVisualizer(typeof(DemoObjectVisualiser), Description="Jonesy's amazing visualiser")]
[Serializable]
public class DemoObject
The DemoObject is now able to be visualised in the DemoObjectVisualiserForm; run the client code and break when the DemoObject instance has been created. When you view the instance in the debugger, you'll see a small magnifying glass icon:
Click this, and your object is displayed in the form:
As well as being visualised, the object can be updated via the form. To demonstrate this, add a button to the form and in its Click event handler, hide the form.
Set the Modifiers property of the picture box to Public; this is a rough hack to enable it to be visible outside of the form. Add a Click event handler to the picture box, and add the following code to update the colour:
private void colourBox_Click(object sender, EventArgs e)
{
//When the colour is clicked, show the dialog to change it
var colordialog = new ColorDialog();
var result = colordialog.ShowDialog();
if (result == System.Windows.Forms.DialogResult.OK)
{
colourBox.BackColor = colordialog.Color;
}
}
The last step to get the colour change reflected in the debugger is to update the object. Add the last few lines here to the Show method in the DemoObjectVisualiser object:
public class DemoObjectVisualiser : DialogDebuggerVisualizer
{
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{
//make sure the object is the correct type
var objectToVisualise = objectProvider.GetObject() as DemoObject;
//Show the visualiser
var form = new DemoObjectVisualiserForm(objectToVisualise);
windowService.ShowDialog(form);
//If the object is replaceable, update the colour
if (objectProvider.IsObjectReplaceable)
{
objectToVisualise.Colour = form.colourBox.BackColor;
objectProvider.ReplaceObject(objectToVisualise);
}
}
}
This will update the debugged object with the new colour, when the save button is clicked:
When an object is viewed in the debugger, by default each value displayed is the result of the class, struct, property or fields's ToString() method. As an example, this object:
would be displayed like this in the Watch / Locals windows:
If you want to display more useful information, you can decorate the class, struct, property or field with the DebuggerDisplay attribute. The attribute's constructor takes a string parameter, which specifies the caption to display. The string can contain expressions to evaluate, if enclosed in curly braces; expressions can include properties of the object being displayed. This example shows the class display as a string containing the contents of the name property, and the IntArray display showing the size of the array.
The object with the attributes is displayed like this:
This attribute can be applied to fields or properties of an object, to determine how they are displayed in the Visual Studio debugger windows.The constructor has a single parameter, of type DebuggerBrowsableState. this is an enum with three values: Never, Collapsed and RootHidden. The "default" for objects without this attribute is to be shown as Collapsed.
As an example, an object I've contrived that looks like this:
would be displayed in the debugger like this:
We can hide properties and fields from the debugger by decorating them with the attribute, initialised with "Never" - note the usage on a private field, which enforces the concept of encapsulation; a principle that from now on I'll be calling ASYPIP (Avoid showing your privates in public)
This object is displayed like this:
The last value of DebuggerBrowsableState is RootHidden - this does not show the property or field itself, but shows its child elements as direct descendants of the object. So, an object decorated like this...