Safely handling exceptions in C #
Structural exceptions are one of the key mechanisms for handling erroneous (including exceptional ones) situations. Listed below are some programming guidelines that improve overall code quality when working with exceptions in C # and, more broadly, the .NET platform.
Own class . Throw exceptions based on your own class, inherited from
Separate fields . Create separate fields in your own class to convey essential information, instead of serializing and deserializing the data in the field
Messages to the log . Log the message whenever the handler catches
Exact class . Use the least common class to catch exceptions, otherwise it can lead to hard-to-detect errors. Consider the following code:
If we assume that the code of classes
Substantial processing. Always handle exceptions informatively. View code
hides problems, preventing them from being detected during debugging or execution.
Cleaning in the block
As you can see, the code deleting the file is duplicated. To avoid repetition, you need to delete the temporary file from the block
The operator
Using an operator is
The result of the function . Do not use exceptions to return the result of the function and do not use special return codes to handle errors. To each his own. The result of the function must be returned, and errors that cannot be skipped must be handled with exceptions.
Lack of resource . Return
Starting place . Keep track of where the exception occurred. For instance,
In the case of a call, the
Adding meaning . Change the original exception only to add the meaning the user needs. For example, in the case of connecting to the database, the user may not care about the reason for the connection failure (socket error), enough information about the failure itself.
Serialization . Make inherited exceptions
Standard constructors . Implement standard constructors, otherwise the correct exception handling can be difficult. So for the exception,
Based on materials from MSDN, CodeProject, StackOverflow.
Own class . Throw exceptions based on your own class, inherited from
Exception
, rather than directly on the basis Exception
, because it allows you to define your own handler and separate the tracking and handling of exceptions thrown by your code and the code of the .NET framework. Separate fields . Create separate fields in your own class to convey essential information, instead of serializing and deserializing the data in the field
Message
. Although the idea of packing inMessage
complex data in the form of a string like JSON looks seductive, this is rarely a good idea, because it adds an additional expense of resources for encoding, localization, decoding. Messages to the log . Log the message whenever the handler catches
Exception
, using a call Exception.ToString();
this will simplify tracking when debugging exceptions. Exact class . Use the least common class to catch exceptions, otherwise it can lead to hard-to-detect 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 classes
SimpleClass
and SimpleLibrary
is located in a separate assembly, in the case when the two assemblies are properly installed, the code is executed correctly, the message "Success 0", and if the assembly the class SimpleLibrary
is not installed, then the code is executed correctly, the message " Failure 1 ”, despite the fact that no error ReportStatus
occurs during the execution of the function . The problem is not obvious due to overly generalized exception handling. The code formatting the line throws exceptions ArgumentNullException
and FormatException
, therefore, it is these exceptions that should be caught in the catch block, then the cause of the error will become obvious - this is an exception FileNotFoundException
due to the absence or incorrect installation of the assembly containing the class SimpleLibrary
. Substantial processing. Always handle exceptions informatively. View code
try
{
DoSomething();
}
catch (SomeException)
{
// TODO: ...
}
hides problems, preventing them from being detected during debugging or execution.
Cleaning in the block
finally
. Delete temporary objects in blocks finally
. 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 delete the temporary file from the block
finally
.void WorkWithFile(string filename)
{
try
{
using (StreamWriter sw = new StreamWriter(filename))
{
// TODO: Do something with temporary file
}
}
finally
{
File.Delete(filename);
}
}
The operator
using
. Use an operator using
. It guarantees a method call Dispose
, even if an exception occurs when calling methods in an object.using (Font f = new Font("Times New Roman", 12.0f))
{
byte charset = f.GdiCharSet;
}
Using an operator is
using
equivalent to a block try/finally
, 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 to handle errors. To each his own. The result of the function must be returned, and errors that cannot be skipped must be handled with exceptions.
Lack of resource . Return
null
if there is no resource. According to the convention generally accepted for .NET API, functions should not throw exceptions in the absence of a resource, they should return null
. This GetManifestResourceStream
returns null
if resources were not specified at compilation or are not visible to the calling code. Starting place . Keep track of where the exception occurred. For instance,
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, the
throw e;
information about where the exception was thrown will be replaced by a new line, since creating a new exception will clear the call stack. Instead, you need to make a call throw;
that simply throws the original exception. Adding meaning . Change the original exception only to add the meaning the user needs. For example, in the case of connecting to the database, the user may not care about the reason for the connection failure (socket error), enough information about the failure itself.
Serialization . Make inherited exceptions
Exception
serializable with [Serializable]
. This is useful since 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 the correct exception handling can be difficult. So for the exception,
NewException
these constructors are as follows:public NewException();
public NewException(string);
public NewException(string, Exception);
protected or private NewException(SerializationInfo, StreamingContext);
Based on materials from MSDN, CodeProject, StackOverflow.