Debugging Features in C# 3.0 Part 1

Overview

C# 3.0 introduces many new constructs and opens entirely new ways of thinking and developing code. In this article I will talk about the new debugging features that make it easy to see the running code and better understand it. In my experience one spends as much time writing the initial code as debugging it later. In fact stepping though the code and inspecting variables and expressions is one of the best ways to understand it. So its not just the original writer but many more who will eventually debug it. To this end there are many new things that you will notice when you start debugging in a C# 3.0 project, I will enumerate few of them in this part of the article.

Results View

The primary aim of the results view is to provide a array like view for Enumerable objects. This allows the user to see the items that are the results of the Query. The Query/Enumeration might be on a local collection(Linq/Xlinq/iterators) or from a remote store(Dlinq/customProviders). The idea here is not to change/hide the object's default structure while adding a simple way to inspect the result. This is quite different from debugger proxies which hide the actual view of the object under a "raw" node and show the users the custom view. The rational being that there are many useful properties/fields on the users query and we wanted to add this functionality without loosing all that information. This brings up 2 interesting questions.

1. When is this applied?

The Results View is applied to an object/struct if

  1. It does not have a Debugger type proxy 
  2. Does not implement IList or ICollection (they already have items view)  
  3. It does implement IEnumerable or IEnumerable<T>.

2. What does the View look like?

  1. For a object whose compile time type is an interface of type IEnumerable, IEnumerable<T>, IQueryable or IQueryable<T>, we hide the derived node and show the runtime type’s  members directly under the object  .
  2. A new node called “Results View” is added to the Expression which when expanded will enumerate the Enumerable.    
  3. The Results View's value column warns the user that expanding will change the state of the object being inspected.
  4. A new format specifier is added to represent the view and can be accessed by typing object/expression, results.

E.g. for query like

int[] array  = new int[]{ 23, 3,54, 8, 10, 39, 87, 3, 7};
var q = from i in array.AsQueryable()
        let y = i * i
        let z = y * y
        select new { y, i, z };

System.Diagnostics.Debugger.Break();

here we can see the Results View Node is added as a child of the Query q. Though q has a static type of IQueryable<SomeAnontype>, we directly show the members of the actual runtime type. The value column has a important message for the user, stating that expanding this view will enumerate the object.If the base of the expression in watch is also an enumerable the results view is added to it. If a field/property which implements IEnumerable is hidden then the results view inherits the hidden property from its parent.

Finally we can also access the results view by using the results specifier “, results”.

There are some exceptions to the rule, Sytem.String is excluded from having this view though it implements IEnumerable.

Extension Methods

Extension methods can now be used when calling an instance method in the watch and the immediate window. This is very useful if you want to  see

  1. Which extension method will get called if the statement was placed here in the code.
  2. The results of a Extension method not present in the code being executed.
  3. Limit the number of elements returned by a query, before expanding its results.
  4. Most importantly since Queries boil down to extension methods, we can use the watch/immediate like scratch pad to try out some Ad-hoc Query like behavior.  

Consider the following code

    public class Program
    {
        static void Main(string[] args)
        {
            int[] array = new int[] { 1, 23, 45, 67, 12 };      // simple in memory array

            Func<Func<int, bool>, IEnumerable<int>> where = array.Where<int>;  //curry an Extension method

            System.Func<int, int> f = Program.identity;
            System.Diagnostics.Debugger.Break();
        }

        public static bool Eval(int i){ return i > 5; }

        public static bool Eval1(int val) { return val < 50; }

        public static int identity(int x) { return x; }
    }

Trim the Enumerable before inspecting it ( very useful if this call will be remoted to the provider of the extension method)

Calling an Extension methods with a method (predicate) as argument

Using Curried delegate from the code

Chaining extension methods to archive Ad-hoc query like execution

This assumes that the assembly contain the extension method is already loaded by an earlier runtime call.

All in all using these 2 features in combination allows users to do some powerful analysis on a  program while it executes. Though not as powerfully as a full fledged Query Analyzer with lambdas, it does allows the user to do very interesting stuff. I am interested in knowing potential pain points that you come across, things that are in your way when debugging etc.

In the coming article i will cover Stepping, Anonymous-types, Range-variables and Xlinq Support.

kick it on DotNetKicks.com