Debugging LINQ in 2017: LINQPad vs. OzCode

LINQ is amazing, it give C# developers the ability to write readable, concise, declarative code. Since it was introduced into the C# language LINQ has become widely used and loved. Unfortunately, there’s one issues when using LINQ as anyone who ever tried to debug LINQ knows – it’s hard, sometimes impossible, to effectively debug LINQ queries.

Over the years several developer attempted to debug LINQ using cool and innovative trickery – this post attempts to show several of the more effective tricks and tools and check what are their pros and cons.

Refactor the LINQ away

For many years, my LINQ debugging modus operandi was to quickly refactor the offending LINQ query into a bunch of foreach loops, find the bug, fix it and then refactor back to LINQ – if time permits.

It’s not a perfect solution – far from it, but no matter how much I enjoyed the clean, concise way LINQ managed to do things – but I had work to do and bugs to fix.

For example, if we take the LINQ demo from our GitHub repository we can transform this:

var mostFrequentWord = text
 .Split(' ', '.', ',')
 .Where(i => i != "")
 .GroupBy(i => i)
 .OrderBy(i => i.Count())
 .Last();

To this tangle of code:

var splitText = text.Split(' ', '.', ',');
var wordsDictionary = new Dictionary<string, int>();
foreach(var word in splitText)
{
	if(word != "")
	{
		if(!wordsDictionary.ContainsKey(word)
		{
			wordsDictionary.Add(word, 0);
		}

		wordsDictionary[word]++;
	}
}

string mostUsedWord = null;
int max = 0;
foreach(var entry in wordsDictionary)
{
	if(entry.value > max)
	{
		word = entry.key;
		max = entry.value;
	}
}

This approach is obviously flawed and so I’ve kept on looking for a better solution.

Using breakpoints and watch windows

The LINQ debugging story slightly improved with Visual Studio 2015. We can use the Immediate Window to run LINQ queries and put parts of the query inside the watch window. And of course you can put breakpoints in the middle of the LINQ clause (even before VS2015). Although a big improvement in the right direction, I would hesitate to call this approach “LINQ debugging”. For starters,  you’d have to take each part of the LINQ query and paste it into the Watch window manually. Using breakpoints in LINQ is always frustrating because while you can put breakings inside lambda expressions, it’s nearly impossible to understand how code works from a small part of it in a specific point in time. In order to properly debug LINQ we need a better overview that we can trust.

LINQPad

LINQPad is an awesome tool that can be used for many tasks – from running small code snippets to performing DB operations. LINQPad also have a build in logging/debugging via Dump, which can be used to trace the program execution:

var mostFrequentWord = text
 .Split(' ', '.', ',') 
 .Where(i => i != "")
 .Dump("Where")
 .GroupBy(i => i) 
 .OrderBy(i => i.Count())
 .Dump("OrderBy")
 .Last();

mostFrequentWord.Key.Dump();

Would create the following output:

linqpad

By carefully studying the output, you might find the issue or at least gain a much needed understanding to what the query does. LINQPad is also a tremendous learning tool – it’s the ultimate code scratchpad where you can run small snippets of C# code and figure out how they work.

If you prefer to use your own trusted Visual Studio you can create an extension method that would provide similar data. If you need to see the implementation of such extension method – check out Michael Sorens blog post. But be warned – you’ll need to dig through a lot of data in order to find the issue’s root cause.

OzCode’s LINQ debugging

Since LINQ debugging proved to be such a headache (if you don’t agree – scroll up and re-read this post) some companies have decided to completely forbid LINQ usage due to lack of “debuggability”. As a .NET developer I find that unacceptable, as I want to benefit from the clear, concise LINQ way. That’s why one of the top features on OzCode’s backlog was “enable LINQ debugging”.

Debugging LINQ consists from three parts:

First, there’s the numeric indicators

numericindicators

Those show how many items exist after each phase. For example after the Where clause where we throw away all of the empty strings we have 29 such items (words).

Pressing one of the numeric indicators will open a new window with detailed information on the items before and after the specific operator and finally there’s a new tool windows that enable choosing items and following them between the different stages of the query debugged. We can choose if we want to see the items that came in, or the items which came out of the where clause.

Which means that all of the information exist at your fingertips to be searched and analyzed.

Debugging exceptions

One of the pains of using debugging LINQ is when of the steps inside the query throws an exception. When that happens it usually means we need to analyze ther query, add logging and hope for the best. I wanted to see how LINQPad handles exception and so I wrote a simple code snippet that has an exception built in:

linqexpception_linqpad

The important bit here is that the result only contains 5 items which means that the problematic item is in fact items number six. I guess I can work with that.

Let’s see how OzCode behaves when an exception is thrown during LINQ:

exceptioninling

Just like LINQPad, OzCode shows the “valid” items, although it also shows the problematic one. The main difference is that all of the LINQ debugging capabilities still work along and help pinpoint the exact result.

Conclusion

So there you have it. Debugging LINQ is no longer impossible. It’s all a matter of using the right tool.

The problem with using tracing.logging and breakpoints is that it’s hard to visualize the flow of the whole query, it might work for specific bugs but usually it’s not enough for “real world” scenarios.

I found LINQPad to be just wonderful for cases where I’m working with a real database, or want to a quick scratchpad to brainstorm ideas on a code snippet. For more complex LINQ queries that are inside the .sln file I actually live in day-to-day, I have not found any tool which comes close to what we’ve created in OzCode.