Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon

Debugging Your .NET Application

Save for later
  • 13 min read
  • 21 Jul 2016

article-image

In this article by Jeff Martin, author of the book Visual Studio 2015 Cookbook - Second Edition, we will discuss about how but modern software development still requires developers to identify and correct bugs in their code. The familiar edit-compile-test cycle is as familiar as a text editor, and now the rise of portable devices has added the need to measure for battery consumption and optimization for multiple architectures. Fortunately, our development tools continue to evolve to combat this rise in complexity, and Visual Studio continues to improve its arsenal.

(For more resources related to this topic, see here.)

Multi-threaded code and asynchronous code are probably the two most difficult areas for most developers to work with, and also the hardest to debug when you have a problem like a race condition. A race condition occurs when multiple threads perform an operation at the same time, and the order in which they execute makes a difference to how the software runs or the output is generated. Race conditions often result in deadlocks, incorrect data being used in other calculations, and random, unrepeatable crashes. The other painful area to debug involves code running on other machines, whether it is running locally on your development machine or running in production. Hooking up a remote debugger in previous versions of Visual Studio has been less than simple, and the experience of debugging code in production was similarly frustrating.

In this article, we will cover the following sections:

  • Putting Diagnostic Tools to work
  • Maximizing everyday debugging

Putting Diagnostic Tools to work

In Visual Studio 2013, Microsoft debuted a new set of tools called the Performance and Diagnostics hub. With VS2015, these tools have revised further, and in the case of Diagnostic Tools, promoted to a central presence on the main IDE window, and is displayed, by default, during debugging sessions. This is great for us as developers, because now it is easier than ever to troubleshoot and improve our code. In this section, we will explore how Diagnostic Tools can be used to explore our code, identify bottlenecks, and analyze memory usage.

Getting ready

The changes didn't stop when VS2015 was released, and succeeding updates to VS2015 have further refined the capabilities of these tools. So for this section, ensure that Update 2 has been installed on your copy of VS2015. We will be using Visual Studio Community 2015, but of course, you may use one of the premium editions too.

How to do it…

For this section, we will put together a short program that will generate some activity for us to analyze:

  1. Create a new C# Console Application, and give it a name of your choice.
  2. In your project's new Program.cs file, add the following method that will generate a large quantity of strings:
    static List<string> makeStrings()
    {
      List<string> stringList = new List<string>();
      Random random = new Random();
    
      for (int i = 0; i < 1000000; i++)
      {
        string x = "String details: " + (random.Next(1000,     100000)); 
        stringList.Add(x);
      }
      return stringList;
    }

  3. Next we will add a second static method that produces an SHA256-calculated hash of each string that we generated. This method reads in each string that was previously generated, creates an SHA256 hash for it, and returns the list of computed hashes in the hex format.
    static List<string> hashStrings(List<string> srcStrings)
      {
      List<string> hashedStrings = new List<string>();
      SHA256 mySHA256 = SHA256Managed.Create();
    
      StringBuilder hash = new StringBuilder();
      foreach (string str in srcStrings)
      {
        byte[] srcBytes =     mySHA256.ComputeHash(Encoding.UTF8.GetBytes(str), 0,     Encoding.UTF8.GetByteCount(str));
        foreach (byte theByte in srcBytes)
        {
          hash.Append(theByte.ToString("x2"));
        }
        hashedStrings.Add(hash.ToString());
        hash.Clear();
      }
      mySHA256.Clear();
      return hashedStrings;
    }
    

  4. After adding these methods, you may be prompted to add using statements for System.Text and System.Security.Cryptography. These are definitely needed, so go ahead and take Visual Studio's recommendation to have them added.
  5. Now we need to update our Main method to bring this all together. Update your Main method to have the following:
    static void Main(string[] args)
    {
      Console.WriteLine("Ready to create strings");
      Console.ReadKey(true);
      List<string> results = makeStrings();
      Console.WriteLine("Ready to Hash " + results.Count() + "   strings ");
      //Console.ReadKey(true);
      List<string> strings = hashStrings(results);            
      Console.ReadKey(true);
    }
    

  6. Before proceeding, build your solution to ensure everything is in working order.
  7. Now run the application in the Debug mode (F5), and watch how our program operates.

    By default, the Diagnostic Tools window will only appear while debugging. Feel free to reposition your IDE windows to make their presence more visible or use Ctrl + Alt + F2 to recall it as needed.

  8. When you first launch the program, you will see the Diagnostic Tools window appear. Its initial display resembles the following screenshot. Thanks to the first ReadKey method, the program will wait for us to proceed, so we can easily see the initial state. Note that CPU usage is minimal, and memory usage holds constant.

    debugging-your-net-application-img-0

  9. Before going any further, click on the Memory Usage tab, and then the Take Snapshot command as indicated in the preceding screenshot. This will record the current state of memory usage by our program, and will be a useful comparison point later on. Once a snapshot is taken, your Memory Usage tab should resemble the following screenshot:

    debugging-your-net-application-img-1

  10. Having a forced pause through our ReadKey() method is nice, but when working with real-world programs, we will not always have this luxury. Breakpoints are typically used for situations where it is not always possible to wait for user input, so let's take advantage of the program's current state, and set two of them. We will put one to the second WriteLine method, and one to the last ReadKey method, as shown in the following screenshot:

    debugging-your-net-application-img-2

  11. Now return to the open application window, and press a key so that execution continues.
  12. The program will stop at the first break point, which is right after it has generated a bunch of strings and added them to our List object. Let's take another snapshot of the memory usage using the same manner given in Step 9. You may also notice that the memory usage displayed in the Process Memory gauge has increased significantly, as shown in this screenshot:

    debugging-your-net-application-img-3

  13. Now that we have completed our second snapshot, click on Continue in Visual Studio, and proceed to the next breakpoint.
  14. The program will then calculate hashes for all of the generated strings, and when this has finished, it will stop at our last breakpoint. Take another snapshot of the memory usage. Also take notice of how the CPU usage spiked as the hashes were being calculated:

    debugging-your-net-application-img-4

  15. Now that we have these three memory snapshots, we will examine how they can help us. You may notice how memory usage increases during execution, especially from the initial snapshot to the second. Click on the second snapshot's object delta, as shown in the following screenshot:

    debugging-your-net-application-img-5

  16. On clicking, this will open the snapshot details in a new editor window. Click on the Size (Bytes) column to sort by size, and as you may suspect, our List<String> object is indeed the largest object in our program. Of course, given the nature of our sample program, this is fairly obvious, but when dealing with more complex code bases, being able to utilize this type of investigation is very helpful. The following screenshot shows the results of our filter:

    debugging-your-net-application-img-6

    If you would like to know more about the object itself (perhaps there are multiple objects of the same type), you can use the Referenced Types option as indicated in the preceding screenshot. If you would like to try this out on the sample program, be sure to set a smaller number in the makeStrings() loop, otherwise you will run the risk of overloading your system.

  17. Returning to the main Diagnostic Tools window, we will now examine CPU utilization. While the program is executing the hashes (feel free to restart the debugging session if necessary), you can observe where the program spends most of its time:

    debugging-your-net-application-img-7

Again, it is probably no surprise that most of the hard work was done in the hashStrings() method. But when dealing with real-world code, it will not always be so obvious where the slowdowns are, and having this type of insight into your program's execution will make it easier to find areas requiring further improvement.

When using the CPU profiler in our example, you may find it easier to remove the first breakpoint and simply trigger a profiling by clicking on Break All as shown in this screenshot:

debugging-your-net-application-img-8

How it works...

Microsoft wanted more developers to be able to take advantage of their improved technology, so they have increased its availability beyond the Professional and Enterprise editions to also include Community. Running your program within VS2015 with the Diagnostic Tools window open lets you examine your program's performance in great detail.

By using memory snapshots and breakpoints, VS2015 provides you with the tools needed to analyze your program's operation, and determine where you should spend your time making optimizations.

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at £16.99/month. Cancel anytime

There's more…

Our sample program does not perform a wide variety of tasks, but of course, more complex programs usually perform well. To further assist with analyzing those programs, there is a third option available to you beyond CPU Usage and Memory Usage: the Events tab. As shown in the following screenshot, the Events tab also provides the ability to search events for interesting (or long-running) activities.

debugging-your-net-application-img-9

Different event types include file activity, gestures (for touch-based apps), and program modules being loaded or unloaded.

Maximizing everyday debugging

Given the frequency of debugging, any refinement to these tools can pay immediate dividends. VS 2015 brings the popular Edit and Continue feature into the 21st century by supporting a 64-bit code. Added to that is the new ability to see the return value of functions in your debugger. The addition of these features combine to make debugging code easier, allowing to solve problems faster.

Getting ready

For this section, you can use VS 2015 Community or one of the premium editions. Be sure to run your choice on a machine using a 64-bit edition of Windows, as that is what we will be demonstrating in the section.

Don't worry, you can still use Edit and Continue with 32-bit C# and Visual Basic code.

How to do it…

Both features are now supported by C#/VB, but we will be using C# for our examples. The features being demonstrated are compiler features, so feel free to use code from one of your own projects if you prefer. To see how Edit and Continue can benefit 64-bit development, perform the following steps:

  1. Create a new C# Console Application using the default name.
  2. To ensure the demonstration is running with 64-bit code, we need to change the default solution platform.
  3. Click on the drop-down arrow next to Any CPU, and select Configuration Manager...

    debugging-your-net-application-img-10

  4. When the Configuration Manager dialog opens, we can create a new project platform targeting a 64-bit code. To do this, click on the drop-down menu for Platform, and select <New...>:

    debugging-your-net-application-img-11

  5. When <New...> is selected, it will present the New Project Platform dialog box. Select x64 as the new platform type:

    debugging-your-net-application-img-12

  6. Once x64 has been selected, you will return to Configuration Manager. Verify that x64 remains active under Platform, and then click on Close to close this dialog. The main IDE window will now indicate that x64 is active:

    debugging-your-net-application-img-13

  7. With the project settings out of the face, let's add some code to demonstrate the new behavior. Replace the existing code in your blank class file so that it looks like the following listing:
    class Program
    {
      static void Main(string[] args)
      {
        int w = 16;
        int h = 8;
        int area = calcArea(w, h);
        Console.WriteLine("Area: " + area);
      }
    
      private static int calcArea(int width, int height)
      {
        return width / height;
      }
    }
    

  8. Let's set some breakpoints so that we are able to inspect during execution. First, add a breakpoint to the Main method's Console line. Add a second breakpoint to the calcArea method's return line. You can do this by either clicking on the left side of the editor window's border, or by right-clicking on the line, and selecting Breakpoint | Insert Breakpoint:

    debugging-your-net-application-img-14

  9. If you are not sure where to click, use the right-click method, and then practice toggling the breakpoint by left-clicking on the breakpoint marker. Feel free to use whatever method you find most convenient. Once the two breakpoints are added, Visual Studio will mark their location as shown in the following screenshot (the arrow indicates where you may click to toggle the breakpoint):

    debugging-your-net-application-img-15

  10. With the breakpoint marker now set, let's debug the program. Begin debugging by either pressing F5, or by clicking on the Start button on the toolbar:

    debugging-your-net-application-img-16

  11. Once debugging starts, the program will quickly execute until stopped by the first breakpoint. Let's first take a look at Edit and Continue. Visual Studio will stop at the calcArea method's return line. Astute readers will notice an error (marked by 1 in the following screenshot) present in the calculation, as the area value returned should be width * height. Make the correction.
  12. Before continuing, note the variables listed in the Autos window (marked by 2 in the following screenshot). (If you don't see Autos, it can be made visible by pressing Ctrl + D, A, or through Debug | Windows | Autos while debugging.)

    debugging-your-net-application-img-17

  13. After correcting the area calculation, advance the debugging step by pressing F10 twice. (Alternatively make the advancement by selecting the menu item Debug | Step Over twice). Visual Studio will advance to the declaration for the area. Note that you were able to edit your code and continue debugging without restarting.
  14. The Autos window will update to display the function's return value, which is 128 (the value for area has not been assigned yet in the following screenshot—Step Over once more if you would like to see that assigned):

    debugging-your-net-application-img-18

There's more…

Programmers who write C++ have already had the ability to see the return values of functions—this just brings .NET developers into the fold. The result is that your development experience won't have to suffer based on the language you have chosen to use for your project.

The Edit and Continue functionality is also available for ASP.NET projects. New projects created on VS2015 will have Edit and Continue enabled by default. Existing projects imported to VS2015 will usually need this to be enabled if it hasn't been done already. To do so, open the Options dialog via Tools | Options, and look for the Debugging | General section. The following screenshot shows where this option is located on the properties page:

debugging-your-net-application-img-19

Whether you are working with an ASP.NET project or a regular C#/VB .NET application, you can verify Edit and Continue is set via this location.

Summary

In this article, we examine the improvements to the debugging experience in Visual Studio 2015, and how it can help you diagnose the root cause of a problem faster so that you can fix it properly, and not just patch over the symptoms.

Resources for Article:

 


Further resources on this subject: