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 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 inMessagecomplex 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 SimpleClassand SimpleLibraryis 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 SimpleLibraryis not installed, then the code is executed correctly, the message " Failure 1 ”, despite the fact that no error ReportStatusoccurs during the execution of the function . The problem is not obvious due to overly generalized exception handling. The code formatting the line throws exceptions ArgumentNullExceptionand 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 blockfinally . 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 operatorusing . 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 usingequivalent 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 nullif 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 GetManifestResourceStreamreturns nullif 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 Exceptionserializable 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, NewExceptionthese 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.

Also popular now: