Dotnet logo

.NET Tools

Essential productivity kit for .NET and game developers

How-To's

Running and debugging a .NET project in Rider

JetBrains Rider - cross-platform .NET IDEAs developers, a good share of our time is spent debugging the software we are writing. The debugger is an invaluable tool not only to track down bugs, but also to help us understand what a piece of code is doing when it’s being executed.

Rider comes with an excellent debugger which allows attaching to a new or existing process and lets us place breakpoints to pause the application and inspect variables, the current call stack and so on. It supports all .NET frameworks, .NET Core, Mono, Xamarin, Unity, ASP.NET and ASP.NET Core, in standalone apps, web apps and unit tests.

In this three-post series, we’ll look deeper at what we can do with Rider’s debugger and how it can help us debug our code as efficient as possible. We’ll focus on debugging .NET code, but do know a lot of these features are also available for debugging JavaScript and TypeScript.

In this series:

The table of contents will be updated as we progress. Keyboard shortcuts described in this post are based on the Visual Studio keymap.

This post will start at the very beginning: how can we debug code? How can be inspect variables and step through/step over code and control how it’s being executed?

Debugging 101

Let’s start with exploring the debugger using a simple application. In the application, we load a file containing a list of people and the company they work for, loop over them and print the output to console:

internal class Program
{
    public static void Main(string[] args)
    {
        var json = File.ReadAllText("people.json");
        var people = JsonConvert.DeserializeObject<List<Person>>(json);

        PrintPeople(people);

        Console.WriteLine("Press <enter> to quit.");
        Console.ReadLine();
    }

    private static void PrintPeople(List<Person> people)
    {
        foreach (var person in people)
        {
            PrintPerson(person);
        }
    }

    private static void PrintPerson(Person person)
    {
        var name = person.Name;
        Console.WriteLine(name);
    }
}

Very often, debugging involves inspecting the content of variables at a particular point during execution. To inspect a variable at such point, we need to tell the debugger at which point to pause or break the running application: a breakpoint.

Let’s set a breakpoint where we print the name of our person to the console. We can do this by clicking the left gutter, which will display a red bullet that means the line of code has a breakpoint. Breakpoints can also be toggled using the F9 key.

Toggle breakpoint in Rider

Once we have one (or more) breakpoints in our application, we can run our application with the debugger attached. This can be done from the Run | Debug menu, or simply by pressing F5. Rider will then compile our application, start the selected run/debug configuration (more on those later in this series), and attach the debugger to it. When execution arrives at the statement where we added our breakpoint, it will pause execution.

There are several things we can see when a breakpoint is hit:

  • The statement that is about to be executed is highlighted in the editor.
  • The editor displays the value for variables that have been assigned next to our code. For example we see the method argument person contains a DebuggingDemo.Person, and name is “Maarten Balliauw”.
  • The Debug tool window at the bottom shows the current call stack on the left, and variables that are in scope on the right.

Overview of the debugger tool window after hitting a breakpoint

The Debug tool window

In the debug tool window, we can do several things. We can see the call stack for the various threads on the left, and inspect contents of variables on the right. The toolbar allows us to resume execution until the next breakpoint is hit, step over, step into and so forth. We’ll cover these in a bit.

The Frames pane on the left shows us the current frames as a call stack. When .NET code executes, whenever we enter a function a new frame is pushed on the call stack, capturing information such as the arguments given to the function, local variables and so on. The deeper we go into the chain of functions in our code, the more entries will be shown in the call stack.

We can click a call stack entry and jump to the function in code. This helps understanding how we arrived at our breakpoint. Note that the variables displayed inline and in the variables tool window will also show the value for variables at that point in the stack.

Walking the stack in Rider's debug tool window

From the variables pane, we can drill into the object structure. For example for our Person object we can expand the Company property and look into the Company’s Name property.

Expand variable in Debug tool window

Sometimes it can also be useful to update the value of a variable at runtime to see how the application behaves or to reproduce a potential bug. We can do this from the variables pane as well using the Set Value… context menu (F2). After updating the value, the debugger will display the new value inline and in tool windows, and our application will also make use of it:

Set vakue from debug tool window

From the tool window we can also add a watch. A watch allows us to inspect a specific variable or expression. For example we could add a watch for person.Email which would display that specific property value in the variables pane. We can also watch expressions and, for example, add a check to verify a given condition is true. This can help us figure out the value of a variable or evaluate an expression while stepping through code, making it easier to inspect the value. Note that code completion is provided when adding watches.

Add watch to track variable value

Rider intelligently visualizes the variable based on information available. It’s also possible to annotate types with the DebuggerDisplayAttribute attribute, so we can control how a type or member is displayed inline with our code or in the debugger tool window. For example for our Person class, we can add a DebuggerDisplay attribute which visualizes our type based on some of its properties:

[DebuggerDisplay("{Name} ({Email})")]
public class Person
{
    public string Name { get; set; }
    public string Email { get; set; }
    public Company Company { get; set; }
}

Once added, the debugger will make use of this attribute to determine the format of data displayed while debugging. In both the editor and the variables window, we now see the name and e-mail of our person instead of the type name as seen in earlier screenshots:

Using DebuggerDisplayAttribute to visualize a variable in the debugger

Stepping through code

While debugging, we can execute code statement by statement, allowing us to inspect what happens in our application at every statement. The debug tool window has several toolbar buttons (with keyboard shortcuts) that let us step through our code.

Commonly used actions are:

  • Resume program (F5) – resumes execution until the next breakpoint is hit (or the program terminates).
  • Step over (F10) – executes the method but does not step into its body.
  • Step into (F11) – starts executing the method and steps into its body.

Step over vs. Step into

There are a couple more ways of stepping through our code. When we’ve stepped into a function, we may want to resume execution of the function and return to the parent frame in the call stack and then pause execution. This can be done using Step out (Shift+F11).

Using Run to cursor (Ctrl+F10), we can set the cursor on a specific statement and have Rider’s debugger resume execution to that cursor position, ignoring existing breakpoints on the way there.

One of my personal favorites is Set next statement (Ctrl+Alt+Shift+F10), which allows us to move the execution pointer to an earlier or later location in our code. If we move it to a later location, we will effectively be skipping certain lines of code and prevent them from being executed. If we move it to an earlier location, code will resume executing code from that location. This lets us, for example, repeat a certain action in code without having to restart the debugger. We’ll place a breakpoint at the end of our method, and use Set next statement (Ctrl+Alt+Shift+F10) to run it again from the start.

Set next statement - moving the execution pointer to a given location

In this post, we’ve seen the basics how we can use Rider to debug a .NET project. We’ve seen what a breakpoint is, and how we can step through our code and take a look at the current call stack and inspect variables that are in scope.

In our next post, we’ll go deeper into the concept of run/debug configurations, and how they can help us start a debugging session. We’ll also look into debugging unit tests and how we can attach to any running .NET process and see what’s going on in there. Stay tuned!

Download JetBrains Rider and give it a try! We’d love to hear your feedback.

image description