📜 ⬆️ ⬇️

Safe work with exceptions in C #

Structural exceptions are one of the key mechanisms for handling erroneous (including exceptional situations proper). Below are some programming guidelines that improve the overall quality of the code when dealing with exceptions in C # and, more broadly, the .NET platform.

Own class . Throw exceptions based on your own class inherited from Exception , and not directly on the basis of Exception , because it gives you the opportunity to define your own handler and separate the tracking and handling of exceptions thrown by your code and the code of .NET.

Separate fields . Create separate fields in your own class to transfer essential information, instead of serializing and deserializing data in the Message field. Despite the fact that the idea of ​​packing complex data in a Message in the form of a JSON string is tempting, it is rarely a good idea because it adds an extra expense to the coding, localization, decoding.

Messages to the log . Write to the log message whenever the handler catches Exception , by calling Exception.ToString(); this makes it easier to track when debugging exceptions.
')
Exact class . Use the least common class to catch exceptions, otherwise it can lead to hard-to-find errors. Consider the following code:

 public class SimpleClass { public static string DoSomething() { try { return SimpleLibrary.ReportStatus(); } catch (Exception) { return "Failure 1"; } } } public class SimpleLibrary { public static string ReportStatus() { return String.Format("Success {0}.", 0); } } 


If we assume that the code of the SimpleClass and SimpleLibrary are in separate assemblies, then in the case when both assemblies are installed correctly, the code runs correctly, displaying the message “Success 0”, and if the assembly with the SimpleLibrary class SimpleLibrary not installed, then the code is executed incorrectly, displaying the message “Failure 1”, despite the fact that no error occurs during the execution of the ReportStatus function. The problem is not obvious due to too generalized handling of exceptions. The code formatting the string throws ArgumentNullException and FormatException , so these exceptions should be caught in the catch block, then the reason for the error becomes obvious - this is a FileNotFoundException exception due to the absence or incorrect installation of the assembly containing the SimpleLibrary class.

Content processing . Always handle exceptions meaningfully. View Code

 try { DoSomething(); } catch (SomeException) { // TODO: ... } 


hides problems, not allowing them to be detected during debugging or execution.

Cleaning in the finally block . Delete temporary objects in finally blocks. Consider the operation of writing a temporary file.

 void WorkWithFile(string filename) { try { using (StreamWriter sw = new StreamWriter(filename)) { // TODO: Do something with temporary file } File.Delete(filename); } catch (Exception) { File.Delete(filename); throw; } } 


As you can see, the code deleting the file is duplicated. To avoid repetition, you need to remove the temporary file from the finally block.

 void WorkWithFile(string filename) { try { using (StreamWriter sw = new StreamWriter(filename)) { // TODO: Do something with temporary file } } finally { File.Delete(filename); } } 


Operator using . Use the using statement. It guarantees a call to the Dispose method, even if an exception occurs in the object when the methods are called.

 using (Font f = new Font("Times New Roman", 12.0f)) { byte charset = f.GdiCharSet; } 


Using the using statement is equivalent to a try/finally block, but more compact and concise.

 Font f = new Font("Times New Roman", 12.0f); try { byte charset = f.GdiCharSet; } finally { if (f != null) ((IDisposable)f).Dispose(); } 


The result of the function . Do not use exceptions to return the result of the function and do not use special return codes for error handling. To each his own. The result of the function must be returned, and errors that cannot be skipped must be processed using exceptions.

Lack of resource . Return null if there is no resource. According to the convention common to the .NET API, functions should not throw exceptions when there is no resource; they should return null . So GetManifestResourceStream returns null if resources were not specified during compilation or not visible to the calling code.

Departure location Keep information about the original location of the exception. For example,

 try { // Do something to throw exception } catch (Exception e) { // Do something to handle exception throw e; // Wrong way! throw; // Right way } 


In the case of a call to throw e; information about the place of generation of the exception will be replaced by a new line, since the creation of a new exception will clear the call stack. Instead, call throw; which simply rethrows the original exception.

Adding meaning . Change the original exception only to add meaning to the user. For example, in the case of connecting to the database, the user may be indifferent to the reason for the connection failure (socket error), enough information about the failure itself.

Serialization . Make exceptions inherited from Exception serializable with [Serializable] . This is useful because it is never known in advance where the exception will be received, for example, in a web service.

 [Serializable] public class SampleSerializableException : Exception { public SampleSerializableException() { } public SampleSerializableException(string message) : base(message) { } public SampleSerializableException(string message, Exception innerException) : base(message, innerException) { } protected SampleSerializableException(SerializationInfo info, StreamingContext context) : base(info, context) { } } 


Standard constructors . Implement standard constructors, otherwise proper exception handling can be difficult. So to exclude NewException these constructors are:




Based on MSDN, CodeProject, StackOverflow.

Source: https://habr.com/ru/post/178805/


All Articles