diff --git a/033_Debug_console_apps_2/033_csharp.md b/033_Debug_console_apps_2/033_csharp.md index 9b5e476..ddc3e68 100644 --- a/033_Debug_console_apps_2/033_csharp.md +++ b/033_Debug_console_apps_2/033_csharp.md @@ -1067,3 +1067,508 @@ Here are two important things to remember from this unit: requirements. --- + +## Configure conditional breakpoints in C# + +The C# debugger for Visual Studio Code supports the option to configure a +breakpoint that only triggers if a condition is met. This type of breakpoint is +called a conditional breakpoint. Conditional breakpoints can be configured +directly or by editing an existing breakpoint. + +> Note +> Visual Studio Code also supports a conditional breakpoint that triggers based +on the number of times the breakpoint has been "hit". + +Suppose you're debugging an application that processes product information in a +multidimensional string array. The array includes thousands of data points. The +problem that you're debugging seems to occur for products that are marked as +new. Your code processes the array inside a `for` loop. You need to set a +breakpoint inside the loop, but you only want to pause when products are `new`. + +### Use a standard breakpoint to examine a data processing application + +Replace the contents of your Program.cs file with the following code: + +```cs +int productCount = 2000; +string[,] products = new string[productCount, 2]; + +LoadProducts(products, productCount); + +for (int i = 0; i < productCount; i++) { + string result; + result = Process1(products, i); + if (result != "obsolete") { + result = Process2(products, i); + } +} + +bool pauseCode = true; +while (pauseCode == true) ; +``` + +This code uses a method named `LoadProducts` to load data into the `products` +array. After the data is loaded, the code iterates through the array and calls +methods named `Process1` and `Process2`. + +To generate data for the simulated processes, add the following method to the +end of your Program.cs file: + +```cs +static void LoadProducts(string[,] products, int productCount) { + Random rand = new Random(); + for (int i = 0; i < productCount; i++) { + int num1 = rand.Next(1, 10000) + 10000; + int num2 = rand.Next(1, 101); + string prodID = num1.ToString(); + if (num2 < 91) { + products[i, 1] = "existing"; + } else if (num2 == 91) { + products[i, 1] = "new"; + prodID = prodID + "-n"; + } else { + products[i, 1] = "obsolete"; + prodID = prodID + "-0"; + } + products[i, 0] = prodID; + } +} +``` + +The `LoadProducts` method generates 2000 random product IDs and assigns a value +of `existing`, `new`, or `obsolete` to a product description field. There is +about a 1% chance that the products are marked `new`. + +To simulate data processing, add the following methods to the end of your +Program.cs file: + +```cs +static string Process1(string[,] products, int item) { + Console.WriteLine( + $"Process1 message - working on {products[item, 1]} product" + ); + return products[item, 1]; +} + +static string Process2(string[,] products, int item) { + Console.WriteLine( + $"Process2 message - working on product ID #: {products[item, 0]}" + ); + if (products[item, 1] == "new") { + Process3(products, item); + } + return "continue"; +} + +static void Process3(string[,] products, int item) { + Console.WriteLine( + $"Process3 message - processing product information for 'new' product" + ); +} +``` + +The `Process1` and `Process2` methods display progress messages and return a +string. + +Notice that the `Process2` method calls `Process3` if the product is `new`. + +On the Visual Studio Code File menu, select Save. + +Near the top of the Program.cs file, set a breakpoint on the following code +line: + + ```cs + result = Process1(products, i); + ``` + +Open the RUN AND DEBUG view, and then select **Start Debugging**. + +
+ + ![img](./img/code_oss_debug_09.png) + +
+ +Use Step Into to walk through the code for `Process1` and `Process2`. + +Notice the updates to the VARIABLES and CALL STACK sections of the RUN AND +DEBUG view. + +Continue to use Step Into to walk through the code until you see that `i` is +equal to 3. + +The VARIABLES section of the RUN AND DEBUG view displays the value assigned to +`i`. + +
+ + ![img](./img/code_oss_debug_10.png) + +
+ +Notice that `Process1` and `Process2` display messages to the DEBUG CONSOLE +panel. A real application may require user interactions as data is being +processed. Some interactions may be dependent on the data being processed. + +Use the **Stop** button to stop code execution. + +### Configure a conditional breakpoint using an expression + +A standard breakpoint is great for walking through a data processing +application. However, in this case you're interested in `new` products and you +don't want to walk through the analysis of each product to find the ones that +are `new`. This scenario is a good example of when conditional breakpoints +should be used. + +Right-click your existing breakpoint, and then select `Edit Breakpoint`. + +Enter the following expression: + + ```cs + products[i,1] == "new"; + ``` + +
+ + ![img](./img/code_oss_debug_11.png) + +
+ +Notice that the expression is no longer displayed after you press Enter. + +To display the expression temporarily, hover the mouse pointer over the +breakpoint (red dot). + +
+ + ![img](./img/code_oss_debug_12.png) + +
+ +To run your application with the conditional breakpoint configured, select +**Start Debugging**. + +Wait for the application to pause at the conditional breakpoint. + +Notice the value of `i` displayed under the VARIABLES section. + +On the **Debug controls** toolbar, select **Continue** + +Notice that the value of `i` has been updated the VARIABLES section. + +
+ + ![img](./img/code_oss_debug_13.png) + +
+ +Select **Step Into**. + +Continue selecting **Step Into** until the `Process1` message is displayed. + +Notice that `Process1` reports that it's working on a **new** product. + +
+ + ![img](./img/code_oss_debug_14.png) + +
+ +Take a moment to consider the advantage that conditional breakpoints offer. + +In this simulated data processing scenario, there is about a 1% chance that a +product is `new`. If you're using a standard breakpoint to debug the issue, +you'd need to walk through the analysis of about 100 products to find one of +the `new` products that you're interested in. + +Conditional breakpoints can save you lots of time when you're debugging an +application. + +Use the **Stop** button to stop code execution. + +Congratulations! You successfully configured a conditional breakpoint. + +### Recap + +Here are two important things to remember from this unit: + +- Use a standard breakpoint to pause an application each time a breakpoint is +encountered. +- Use a conditional breakpoint to pause an application when a Boolean +expression evaluates to `true`. + +--- + +## Exercise - Monitor variables and execution flow + +The RUN AND DEBUG view provides developers with an easy way to monitor +variables and expressions, observe execution flow, and manage breakpoints +during the debug process. + +Examine the sections of the Run and Debug view +Each section of the RUN AND DEBUG view provides unique capabilities. Using a combination of these sections during the debug process is often helpful. + +### VARIABLES section + +Monitoring variable state is an important aspect of code debugging. Unexpected +changes in variable state will often help to identify logic errors in your code. + +The VARIABLES section organizes your variables by scope. The `Locals` scope +displays the variables in the current scope (the current method). + +> Note +> The top-level statements section of a console application is considered its +own method. A method named `Main`. + +You can unfold (expand) the displayed scopes by selecting the arrow to the left +of the scope name. You can also unfold variables and objects. The following +screenshot shows the `numbers` array unfolded under the `Locals` scope. + +It's also possible to change the value of a variable at runtime using the +VARIABLES section. You can double-click the variable name and then enter a new +value. + +### WATCH section + +What if you want to track a variable state across time or different methods? It +can be tedious to search for the variable every time. That's where the WATCH +section comes in handy. + +You can select the **Add Expression** button (appears as a plus sign: +) to +enter a variable name or an expression to watch. As an alternative, you can +right-click a variable in the VARIABLES section and select `Add to watch`. + +All expressions inside the WATCH section will be updated automatically as your +code runs. + +### CALL STACK section + +Every time your code enters a method from another method, a call layer is added +to the application's call stack. When your application becomes complex and you +have a long list of methods called by other methods, the call stack represents +the trail of method calls. + +The CALL STACK section is useful when you're trying to find the source location +for an exception or WATCH expression. If your application throws an unexpected +exception, you'll often see a message in the console that resembles the +following: + +```txt +Exception has occurred: CLR/System.DivideByZeroException +An unhandled exception of type 'System.DivideByZeroException' occurred in Debug1.dll: 'Attempted to divide by zero.' + at Program.<
$>g__WriteMessage|0_1() in C:\Users\howdc\Desktop\Debug1\Program.cs:line 27 + at Program.<
$>g__Process1|0_0() in C:\Users\howdc\Desktop\Debug1\Program.cs:line 16 + at Program.
$(String[] args) in C:\Users\howdc\Desktop\Debug1\Program.cs:line 10 +``` + +The indented group of `at Program ...` lines under the error message is called +a stack trace. The stack trace lists the name and origin of every method that +was called leading up to the exception. The information can be a bit difficult +to decipher though, because it can also include information from the .NET +runtime. In this example, the stack trace is pretty clean and you can see that +exception occurred in a method named `WriteMessage`. The stack originates in a +method named `Main`, which is the top-level statements section of the console +application. + +The CALL STACK section can help you to avoid the difficulty of deciphering a +stack trace that's cluttered with .NET runtime information. It filters out +unwanted information to show you only the relevant methods from your own code +by default. You can manually unwind the call stack to find out where the +exception originated. + +### BREAKPOINTS section + +The BREAKPOINTS section displays the current breakpoint settings and can be +used to enable or disable specific breakpoints during a debug session. + +### Configure your application and launch configuration + +When you're working on a console application that reads user input, you'll +probably need to update launch configuration file. + +Update the code in your Program.cs file as follows: + +```cs +string? readResult; +int startIndex = 0; +bool goodEntry = false; + +int[] numbers = { 1, 2, 3, 4, 5 }; + +// Display the array to the console. +Console.Clear(); +Console.Write("\n\rThe 'numbers' array contains: { "); +foreach (int number in numbers) { + Console.Write($"{number} "); +} + +// To calculate a sum of array elements, +// prompt the user for the starting element number. +Console.WriteLine($"}}\n\r\n\rTo sum values 'n' through 5, enter a value for 'n':"); +while (goodEntry == false) { + readResult = Console.ReadLine(); + goodEntry = int.TryParse(readResult, out startIndex); + if (startIndex > 5) { + goodEntry = false; + Console.WriteLine("\n\rEnter an integer value between 1 and 5"); + } +} + +// Display the sum and then pause. +Console.WriteLine( + $"\n\rThe sum of numbers {startIndex} through " + + $"{numbers.Length} is: {SumValues(numbers, startIndex)}" +); +Console.WriteLine("press Enter to exit"); +readResult = Console.ReadLine(); + +// This method returns the sum of elements n through 5 +static int SumValues(int[] numbers, int n) { + int sum = 0; + for (int i = n; i < numbers.Length; i++) { + sum += numbers[i]; + } + return sum; +} +``` + +Take a minute to review the code. + +Notice the following: + +- The code specifies an integer array containing five numbers. +- The code displays output in the console. +- The code prompts the user to enter a starting element number `n` that it uses +to sum array elements `n` through `5`. +- The code calculates the sum in a method, displays the results in the console, +and then pauses. + +> Note +> The DEBUG CONSOLE panel does not support user input from the console. + +On the Visual Studio Code **File** menu, select **Save**. + +On the Run menu, select Remove All Breakpoints. + +This removes any breakpoints left over from the previous exercise. + +On the RUN AND DEBUG view, select Start Debugging. + +Notice that an error occurs when the `Console.Clear();` code line is executed. + +On the **Debug toolbar**, select **Stop**. + +Switch to the EXPLORER view, and then open the launch.json file in the Editor. + +Update the value of the `console` attribute as follows: + +```json +"console":"integratedTerminal", +``` + +On the Visual Studio Code **File** menu, select **Save**, and then close the +launch.json file. + +### Review application output and identify issues + +Reviewing the output of your application can reveal logic issues that you've +overlooked when writing your code. + +Switch back to the RUN AND DEBUG view. + +On the RUN AND DEBUG view, select **Start Debugging**. + +The messages displayed to the DEBUG CONSOLE panel show the debugger attaching +to the `Debug101.dll` application. + +Notice that no error messages are displayed. + +Changing the value of the `console` attribute from **internalConsole** to +**integratedTerminal** in the launch configuration file has fixed the console error. But now you need to locate the console that contains your output. + +In the Panels area below the Editor, switch from the DEBUG CONSOLE panel to the +TERMINAL panel. + +Notice that code execution has paused at the message prompting the user to +enter a value for `n`. + +The output on the TERMINAL panel should look like the following: + + ```txt + The 'numbers' array contains: { 1 2 3 4 5 } + + To sum values 'n' through 5, enter a value for 'n': + ``` + +At the TERMINAL command prompt, enter **3** + +Review the output from the application. + +The output on the TERMINAL panel should look like the following: + + ```txt + The 'numbers' array contains: { 1 2 3 4 5 } + + To sum values 'n' through 5, enter a value for 'n': + 3 + + The sum of numbers 3 through 5 is: 9 + press Enter to exit + ``` + +Take a minute to consider the reported value of `sum` and the values of array +elements 3 through 5 displayed at the top of the console. + +The message says: `The sum of numbers 3 through 5 is: 9`. However, array +elements 3 through 5 are `3`, `4`, and `5`. Shouldn't the reported sum be 12? + +You can use the VARIABLES section of the RUN AND DEBUG view to investigate the +issue. + +### Monitor variable state + +In some cases, simply monitoring variable state is enough to identify the logic +issue in your application. + +Set a breakpoint on the following code line: + +```cs +Console.WriteLine($"\n\rThe sum of numbers {startIndex} through {numbers.Length} is: {SumValues(numbers, startIndex)}"); +``` + +On the RUN AND DEBUG view, select Start **Debugging**. + +Switch from the DEBUG CONSOLE panel to the TERMINAL panel. + +At the TERMINAL command prompt, enter **3** + +Code execution will pause at the breakpoint. + +Take a minute to review the VARIABLES section of the RUN AND DEBUG view. + +Notice that `startIndex` has been assigned the value that you entered, which is +`3`. + +Select **Step Into**. + +Notice that the VARIABLES and CALL STACK sections are updated. + +The CALL STACK section shows that code execution has moved into the `SumValues` +method. + +The VARIABLES section, which lists the local variables, shows the value of the +integer `n`. The method parameter `n` is assigned its value from the method +call argument `startIndex`. In this case, the change to variable names makes it +clear the value has been passed, not a reference pointer. + +> Note +> In this case, you can see most of your code in the Editor, so you might not +need the CALL STACK section, but when you're working on larger applications +with deeply nested and interconnected method calls, the execution path shown in +the CALL STACK section can be extremely useful. + +Continue selecting Step Into until the value assigned to `sum` is no longer `0`. + +Take a minute to review the information shown in the VARIABLES section. + +You should see the following: diff --git a/033_Debug_console_apps_2/Debug101/Debug101.csproj b/033_Debug_console_apps_2/Debug101/Debug101.csproj new file mode 100644 index 0000000..2150e37 --- /dev/null +++ b/033_Debug_console_apps_2/Debug101/Debug101.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/033_Debug_console_apps_2/Debug101/Program.cs b/033_Debug_console_apps_2/Debug101/Program.cs new file mode 100644 index 0000000..69053d5 --- /dev/null +++ b/033_Debug_console_apps_2/Debug101/Program.cs @@ -0,0 +1,41 @@ +string? readResult; +int startIndex = 0; +bool goodEntry = false; + +int[] numbers = { 1, 2, 3, 4, 5 }; + +// Display the array to the console. +Console.Clear(); +Console.Write("\n\rThe 'numbers' array contains: { "); +foreach (int number in numbers) { + Console.Write($"{number} "); +} + +// To calculate a sum of array elements, +// prompt the user for the starting element number. +Console.WriteLine($"}}\n\r\n\rTo sum values 'n' through 5, enter a value for 'n':"); +while (goodEntry == false) { + readResult = Console.ReadLine(); + goodEntry = int.TryParse(readResult, out startIndex); + if (startIndex > 5) { + goodEntry = false; + Console.WriteLine("\n\rEnter an integer value between 1 and 5"); + } +} + +// Display the sum and then pause. +Console.WriteLine( + $"\n\rThe sum of numbers {startIndex} through " + + $"{numbers.Length} is: {SumValues(numbers, startIndex)}" +); +Console.WriteLine("press Enter to exit"); +readResult = Console.ReadLine(); + +// This method returns the sum of elements n through 5 +static int SumValues(int[] numbers, int n) { + int sum = 0; + for (int i = n; i < numbers.Length; i++) { + sum += numbers[i]; + } + return sum; +} diff --git a/033_Debug_console_apps_2/conditional_brakpoints/Program.cs b/033_Debug_console_apps_2/conditional_brakpoints/Program.cs new file mode 100644 index 0000000..e14cef5 --- /dev/null +++ b/033_Debug_console_apps_2/conditional_brakpoints/Program.cs @@ -0,0 +1,57 @@ +int productCount = 2000; +string[,] products = new string[productCount, 2]; + +LoadProducts(products, productCount); + +for (int i = 0; i < productCount; i++) { + string result; + result = Process1(products, i); + if (result != "obsolete") { + result = Process2(products, i); + } +} + +bool pauseCode = true; +while (pauseCode == true) ; + +static void LoadProducts(string[,] products, int productCount) { + Random rand = new Random(); + for (int i = 0; i < productCount; i++) { + int num1 = rand.Next(1, 10000) + 10000; + int num2 = rand.Next(1, 101); + string prodID = num1.ToString(); + if (num2 < 91) { + products[i, 1] = "existing"; + } else if (num2 == 91) { + products[i, 1] = "new"; + prodID = prodID + "-n"; + } else { + products[i, 1] = "obsolete"; + prodID = prodID + "-0"; + } + products[i, 0] = prodID; + } +} + +static string Process1(string[,] products, int item) { + Console.WriteLine( + $"Process1 message - working on {products[item, 1]} product" + ); + return products[item, 1]; +} + +static string Process2(string[,] products, int item) { + Console.WriteLine( + $"Process2 message - working on product ID #: {products[item, 0]}" + ); + if (products[item, 1] == "new") { + Process3(products, item); + } + return "continue"; +} + +static void Process3(string[,] products, int item) { + Console.WriteLine( + $"Process3 message - processing product information for 'new' product" + ); +} diff --git a/033_Debug_console_apps_2/conditional_brakpoints/conditional_brakpoints.csproj b/033_Debug_console_apps_2/conditional_brakpoints/conditional_brakpoints.csproj new file mode 100644 index 0000000..2150e37 --- /dev/null +++ b/033_Debug_console_apps_2/conditional_brakpoints/conditional_brakpoints.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/033_Debug_console_apps_2/img/code_oss_debug_09.png b/033_Debug_console_apps_2/img/code_oss_debug_09.png new file mode 100644 index 0000000..5264bf3 Binary files /dev/null and b/033_Debug_console_apps_2/img/code_oss_debug_09.png differ diff --git a/033_Debug_console_apps_2/img/code_oss_debug_10.png b/033_Debug_console_apps_2/img/code_oss_debug_10.png new file mode 100644 index 0000000..ecfe9d2 Binary files /dev/null and b/033_Debug_console_apps_2/img/code_oss_debug_10.png differ diff --git a/033_Debug_console_apps_2/img/code_oss_debug_11.png b/033_Debug_console_apps_2/img/code_oss_debug_11.png new file mode 100644 index 0000000..7fb6427 Binary files /dev/null and b/033_Debug_console_apps_2/img/code_oss_debug_11.png differ diff --git a/033_Debug_console_apps_2/img/code_oss_debug_12.png b/033_Debug_console_apps_2/img/code_oss_debug_12.png new file mode 100644 index 0000000..62542fb Binary files /dev/null and b/033_Debug_console_apps_2/img/code_oss_debug_12.png differ diff --git a/033_Debug_console_apps_2/img/code_oss_debug_13.png b/033_Debug_console_apps_2/img/code_oss_debug_13.png new file mode 100644 index 0000000..6c68d8f Binary files /dev/null and b/033_Debug_console_apps_2/img/code_oss_debug_13.png differ diff --git a/033_Debug_console_apps_2/img/code_oss_debug_14.png b/033_Debug_console_apps_2/img/code_oss_debug_14.png new file mode 100644 index 0000000..ad33539 Binary files /dev/null and b/033_Debug_console_apps_2/img/code_oss_debug_14.png differ diff --git a/README.md b/README.md index 8483916..f3e9aeb 100644 --- a/README.md +++ b/README.md @@ -37,4 +37,4 @@ Following 30. [Guided project - Plan a Petting Zoo](./030_project_petting_zoo/030_csharp.md) 31. [Challenge project - Create a mini-game](./031_Challenge_mini_game/031_csharp.md) 32. [Debug console applications 1](./032_Debug_console_apps/032_csharp.md) -32. [Debug console applications 2](./032_Debug_console_apps_2/033_csharp.md) +32. [Debug console applications 2](./033_Debug_console_apps_2/033_csharp.md)