How to set up smartphones and PCs. Informational portal
  • home
  • Advice
  • Typical errors of the si sharp code. Debugging and Exception Handling

Typical errors of the si sharp code. Debugging and Exception Handling

Basics of Exception Handling

Errors are not always the fault of the person who codes the application. Sometimes an application generates an error due to end-user actions, or the error is caused by the context of the environment in which the code is running. In any case, you should always expect bugs in your applications and code according to those expectations.

The .NET Framework provides advanced error handling. The C # error handling mechanism allows you to code custom handling for each type of error condition, and also decouple the code that generates errors from the code that handles them.

Whatever is causing the problem, the application will eventually start to behave differently than expected. Before moving on to structured exception handling, let's first take a look at the three most commonly used terms to describe anomalies:

Software bugs

This is usually the name of the errors that the programmer makes. For example, suppose an application is being built using unmanaged C ++. If the dynamically allocated memory is not freed, which is fraught with a memory leak, a programming error occurs.

User errors

Unlike software bugs, custom bugs are usually caused by whoever runs the application, not whoever builds it. For example, an end user entering an incorrectly formatted string in a text field can generate an error of this kind if the code has not been designed to handle the incorrect input.

Exceptions

Exceptions, or exceptions, are commonly referred to as anomalies that can occur at runtime and that are difficult, if not impossible, to anticipate when programming an application. These possible exceptions include attempting to connect to a database that no longer exists, attempting to open a corrupted file, or attempting to communicate with a machine that is currently offline. In each of these cases, there is little the programmer (and the end user) can do about these "exceptional" circumstances.

From the descriptions above, it should be clear that structured exception handling in .NET is a technique designed to deal with exceptions that can be thrown at runtime. Even in the case of programming and user errors that have escaped the eyes of the programmer, however, the CLR will often automatically generate an appropriate exception describing the current problem. The .NET base class libraries define many different exceptions such as FormatException, IndexOutOfRangeException, FileNotFoundException, ArgumentOutOfRangeException, etc.

In .NET terminology, "exception" refers to programming errors, user errors, and runtime errors. Before diving into the details, let's see what role structured exception handling plays and how it differs from traditional error handling techniques.

The Role of Exception Handling in .NET

Before the advent of .NET, error handling in the Windows operating system was a complex mixture of technologies. Many programmers have incorporated their own error handling logic into the context of the application of interest. For example, the development team could define a set of numeric constants to represent known failures and then use those constants as return values ​​for methods.

In addition to tricks invented by the developers themselves, the Windows API defines hundreds of error codes using #define and HRESULT, as well as many variations of simple boolean values ​​(bool, BOOL, VARIANT BOOL, etc.). Moreover, many C ++ developers of COM applications (as well as VB 6) explicitly or implicitly use a small set of standard COM interfaces (like ISupportErrorlnfo. IErrorlnfo or ICreateErrorlnfo) to return meaningful error information to the COM client.

The obvious problem with all of these older techniques is the lack of symmetry. Each of them more or less fits into the framework of one technology, one language and, perhaps, even one project. B.NET maintains a standard technique for generating and detecting errors in the runtime environment called structured exception handling (SEH) .

The beauty of this technique is that it allows developers to take a uniform approach to error handling that is common to all languages ​​targeting the .NET platform. This allows a C # programmer to handle errors in much the same syntactically manner as a VB programmer and a C ++ programmer using C ++ / CLI.

An additional benefit is that the syntax required to throw and catch exceptions outside of assemblies and machines also looks the same. For example, when you write a Windows Communication Foundation (WCF) service in C #, you can throw a SOAP exception for a remote caller using the same keywords that are used to throw an exception within methods in the same application.

Last update: 23.10.2018

Sometimes, when executing a program, errors arise that are difficult to foresee or foresee, and sometimes they are completely impossible. For example, when transferring a file over a network, the network connection may unexpectedly drop. such situations are called exceptions. The C # language provides developers with the ability to handle these situations. The try ... catch ... finally construct is designed for this in C #.

Try () catch () finally ()

When using a try ... catch..finally block, all statements in the try block are executed first. If no exceptions have been thrown in this block, then after its execution the finally block begins to execute. And then the try..catch..finally construct completes its work.

If an exception is thrown in the try block, the normal execution order stops and the CLR starts looking for a catch block that can handle the exception. If the desired catch block is found, then it is executed, and after it finishes, the finally block is executed.

If the required catch block is not found, then when an exception occurs, the program will crash.

Consider the following example:

Class Program (static void Main (string args) (int x = 5; int y = x / 0; Console.WriteLine ($ "Result: (y)"); Console.WriteLine ("End of program"); ​​Console.Read ();))

In this case, the number is dividing by 0, which will result in an exception being thrown. And when we start the application in debug mode, we will see a window in Visual Studio that informs about the exception:

In this window, we see that an exception was thrown, which is of type System.DivideByZeroException, that is, an attempt to divide by zero. Using the View Details item, you can view more detailed information about the exception.

And in this case, the only thing that remains for us is to complete the execution of the program.

To avoid such an abnormal program termination, you should use the try ... catch ... finally construct to handle exceptions. So, let's rewrite the example as follows:

Class Program (static void Main (string args) (try (int x = 5; int y = x / 0; Console.WriteLine ($ "Result: (y)");) catch (Console.WriteLine ("An exception was thrown! ");) finally (Console.WriteLine (" finally block ");) Console.WriteLine (" End of program "); Console.Read ();))

In this case, we will again have an exception in the try block, since we are trying to divide by zero. And reaching the line

Int y = x / 0;

the program will stop running. The CLR finds the catch block and transfers control to that block.

After the catch block, the finally block will be executed.

An exception was thrown! Finally block End of program

Thus, the program will still not perform division by zero and, accordingly, will not display the result of this division, but now it will not crash, and the exception will be handled in the catch block.

It should be noted that a try block is required in this construction. With a catch block, we can omit the finally block:

Try (int x = 5; int y = x / 0; Console.WriteLine ($ "Result: (y)");) catch (Console.WriteLine ("An exception has occurred!");)

Conversely, with a finally block, we can omit the catch block and not handle the exception:

Try (int x = 5; int y = x / 0; Console.WriteLine ($ "Result: (y)");) finally (Console.WriteLine ("finally block");)

However, although from the point of view of C # syntax, this construction is quite correct, nevertheless, since the CLR cannot find the necessary catch block, the exception will not be handled, and the program will crash.

Exception Handling and Conditional Constructions

A number of exceptions can be foreseen by the developer. For example, suppose a program provides input of a number and output of its square:

Static void Main (string args) (Console.WriteLine ("Enter a number"); int x = Int32.Parse (Console.ReadLine ()); x * = x; Console.WriteLine ("Square number:" + x); Console.Read ();)

If the user enters not a number, but a string, some other characters, then the program will fall into an error. On the one hand, this is exactly the situation where you can use a try..catch block to handle a possible error. However, it would be much more optimal to check the validity of the conversion:

Static void Main (string args) (Console.WriteLine ("Enter a number"); int x; string input = Console.ReadLine (); if (Int32.TryParse (input, out x)) (x * = x; Console. WriteLine ("Square number:" + x);) else (Console.WriteLine ("Invalid input");) Console.Read ();)

The Int32.TryParse () method returns true if the transformation can be performed, and false if it cannot. If the conversion is valid, the variable x will contain the entered number. So, without using try ... catch, you can handle a possible exception.

From a performance point of view, using try..catch blocks is more expensive than using conditionals. Therefore, whenever possible, instead of try..catch, it is better to use conditional constructs to check for exceptions.

conditional compilation constants... It allows the definition of constants used later in the WriteIf conditional printing method of the Debug and Trace classes. The power of this mechanism is that constants can be changed in the project configuration file without changing the project code or requiring it to be recompiled.

Debugging and Visual Studio .Net Toolbox

The instrumental environment of the studio provides the programmer with the widest range of possibilities for tracking the progress of calculations and tracking the states in which the process of calculations is located. Since all modern tooling environments are organized in a similar way and are well known to working programmers, I will not dwell on describing the capabilities of the environment.

Exception handling

No matter how reliable the code is written, no matter how thorough the debugging is, there will be specification violations in the version released for production and maintenance. The reasons for this are the above mentioned laws program technicians... The last error remains in the system, there are users who do not know the specifications, and if the specification can be violated, then this event will happen someday. Such exceptional situations continuation of the program either becomes impossible (an attempt to perform an unauthorized division by zero operation, attempts to write to a protected memory area, an attempt to open a non-existent file, an attempt to obtain a non-existent database record), or in a situation that has arisen, the application of the algorithm will lead to erroneous results.

What to do if exceptional situation? Of course, there is always a standard way - to report the error that has occurred and interrupt the program execution. It is clear that this is only acceptable for harmless applications; even for computer games, this method is not suitable, let alone critical applications!

In programming languages ​​for processing exceptional situations a variety of approaches have been proposed.

C / C ++ Exception Handling

The C programming style is characterized by the description of class methods as Boolean functions that return true if the method terminates normally and false if it occurs. exceptional situation... The method call was inlined in the If statement, which handles an error in case of failure of the method completion:

bool MyMethod (...) (...) if! MyMethod () (// error handling) (// normal execution)

The disadvantages of this scheme are clear. First, there is little information about the cause of the error, so additional information must be passed either through the class fields or through the method arguments. Second, the processing block is embedded in each call, which leads to code bloat.

Therefore, in C / C ++, the try / catch block scheme is used, the essence of which is as follows. The section of the program in which there may be exceptional situation, is executed in the form of a guarded try-block. If during its execution occurs exceptional situation, then the execution of the try-block with the classification of the exception is interrupted. One of the catch blocks that matches the type of the exception begins to handle this exception. There are two such schemes used in C / C ++. One of them - resume scheme- corresponds to the so-called structural, or C-exceptions. The second scheme is without renewal- matches C ++ exceptions. In the first scheme, the exception handler - the catch block - returns control to some point in the try block. In the second scheme, control does not return to the try block.

With some syntactic differences resume scheme used in VB / VBA languages.

Exception Handling Scheme in C #

The C # language inherited the C ++ exception scheme, making its own adjustments to it. Let's consider the scheme in more detail and start with the syntax of the construction try-catch-finally:

try (...) catch (T1 e1) (...) ... catch (Tk ek) (...) finally (...)

Wherever a block is syntactically allowed in a module text, the block can be made guarded by adding the try keyword. The try block can be followed by catch blocks called exception handler blocks, there may be several of them, they may be absent. Completes this sequence finally block- finalization block, which may also be absent. This whole construction can be nested - the try-block can include the construction try-catch-finally.

Throwing exceptions. Creating Exception Objects

The body of a try block may contain exceptional situation leading to throwing exceptions... Formally throwing an exception occurs when a throw statement is executed. This statement is most often executed in the bowels of the operating system when the command system or API function cannot do its job. But this statement can be part of the program text of the try-block and be executed when, as a result of the analysis, it becomes clear that further normal operation is impossible.

Syntactically, the throw statement is:

throw [expression]

The throw clause specifies an object of a class that inherits from the Exception class. This is usually a new expression that creates a new object. If it is absent, then the current exception is re-thrown. If an exception is thrown by the operating system, then it classifies the exception itself, creates an object of the corresponding class and automatically fills in its fields.

In the model we are considering, exceptions are objects, the class of which is a descendant of the Exception class. This class and its many descendants are part of the FCL, although they are scattered across different namespaces. Each class defines a specific type of exception in accordance with the classification adopted in the .Net Framework. Here are just a few exception classes from the System namespace: ArgumentException, ArgumentOutOfRangeException, ArithmeticException, BadImageFormatException, DivideByZeroException, OverflowException. The System.IO namespace contains exception classes related to I / O issues: DirectoryNotFoundException, FileNotFoundException, and many others. Names of all exception classes end with the word Exception. You are allowed to create your own exception classes by inheriting them from the Exception class.

When the throw statement is executed, an object te is created, the TE class of which characterizes the current exception, and the fields contain information about the exceptional situation... Execution of the throw statement causes the normal computation process to end there. If this happens in a guarded try-block, then the stage begins "catching" an exception one of the exception handlers.

Catching an Exception

The catch block - the exception handler has the following syntax:

catch (T e) (...)

The class T specified in the catch-block header must belong to exception classes... A catch block with a formal argument e of class T is potentially capable of catching the current exception te of class TE if and only if te is assignment compatible with e. In other words, potential capture means that the assignment e = te is valid, which is possible when TE is a descendant of T. A handler whose class T is the Exception class is universal handler, it is potentially capable of catching any exception, since they are all descendants of it.

There can be many potential invaders; only one catches an exception - the one who is the first in the check list. What is the check order? It's pretty natural. First, the handlers are checked in the order they follow the try block, and the first potential invader becomes active, catching the exception and handling it. This makes it clear that the order of the catch -blocks is extremely important. The first are the most specialized handlers, further as versatility increases. So, first there should be a DivideByZeroException exception handler, followed by Main. If there is no potential exception catcher in it, then the standard handler will be triggered, interrupting the program execution and issuing a corresponding message.

Good day! Today we will talk about how to handle errors in programs written in C #.

I want to note right away that errors in programs can be conditionally divided into two groups: errors at the build time of the program (at the compilation stage) and errors at the runtime of the program. This tutorial will talk about runtime errors!

Let's remember an example from the previous lesson where the user had to enter two integers from the keyboard. Then I still asked to enter exactly numbers and exactly integers. Why? Until, because if you enter in that program not a number, but just text, or not an integer, but a fractional number, then there will be a runtime error! It is for this reason that today I am telling you about the mechanisms and principles of error handling in programs.

So, if we suspect that our program may have run-time errors, for example, due to the fact that the user entered incorrect data, we must take care of handling these errors. The approach to handling such situations consists in the following rules: a block of code is allocated, during the execution of which errors may occur; a block of code is allocated that is responsible for handling the errors that have occurred.

A block of code in which errors may occur is indicated by a keyword try followed by curly braces (which enclose potentially dangerous operations). Such a block should be immediately followed by an error processing block, it is denoted by the keyword catch, and in parentheses after this word, denotes the type of errors that the data block of code processes, after the closing parenthesis, followed by curly braces, inside which the processing is implemented. Schematically, it looks like this:

Try (// Potentially dangerous code block) catch (error_type) (// Error handling) // Further program statements

And it works like this if in the block try, no error occurred, then block catch is not executed, and control is passed on, i.e. following block is executed catch operator. But if inside the block try an error has occurred, then the execution of this block is interrupted and control is transferred to the block catch, and only then the operators following this block are executed.

In practice, after one block try, there may be several blocks catch, for separate handling of different types of errors.

Let's modify the program code from the previous lesson, add control and error handling that may occur when the user enters incorrect data.

// Potentially dangerous try block (// Prompt the user to enter the first number Console.Write ("Enter the first number and press Enter:"); // Get the first line string firstString = Console.ReadLine (); // Convert the first line to number int firstArg = Convert.ToInt32 (firstString); // Prompt the user to enter the second number Console.Write ("Enter the first number and press Enter:"); // Get the second string string secondString = Console.ReadLine (); // Converting the second string to a number int secondArg = Convert.ToInt32 (secondString); // Adding two variables int result = firstArg + secondArg; // Outputting the result Console.WriteLine ("The result of adding the entered numbers:" + result.ToString ()); ) // Error handling block, SystemException is the most common type of error catch (SystemException) (Console.WriteLine ("An error occurred during program execution, probably incorrect data was entered!");)

Now, if the user enters incorrect data instead of an integer, then the error that occurred will be processed. If you insert this code into the " Main ", build and run the program, you will see the following behavior:

And so, in this lesson, basic information was presented regarding the mechanism for handling errors (exceptions) in the C # language. In the next lesson, we will talk about creating and using our own methods (functions), and we will return to the topic of error handling more than once!

Top related articles