开发者

.NET: Avoidance of custom exceptions by utilising existing types, but which?

Consider the following code (ASP.NET/C#):

private void Application_Start(object sender, EventArgs e)
{
    if (!SetupHelper.SetUp())
    {
        throw new ShitHitFanException();
    }
}

I've never been too hesitant to simply roll my own exception type, basically because I have found (bad practice, or not) that mostly a reasonable descriptive type name gi开发者_如何学运维ves us enough as developers to go by in order to know what happened and why something might have happened. Sometimes the existing .NET exception types even accommodate these needs - regardless of the message.

In this particular scenario, for demonstration purposes only, the application should die a horrible, disgraceful death should SetUp not complete properly (as dictated by its return value), but I can't find an already existing exception type in .NET which would seem to suffice; though, I'm sure one will be there and I simply don't know about it.

Brad Abrams posted this article that lists some of the available exception types. I say some because the article is from 2005, and, although I try to keep up to date, it's a more than plausible assumption that more have been added to future framework versions that I am still unaware of.

Of course, Visual Studio gives you a nicely formatted, scrollable list of exceptions via Intellisense - but even on analysing those, I find none which would seem to suffice for this situation...

ApplicationException: ...when a non-fatal application error occurs

The name seems reasonable, but the error is very definitely fatal - the app is dead.

ExecutionEngineException: ...when there is an internal error in the execution engine of the CLR

Again, sounds reasonable, superficially; but this has a very definite purpose and to help me out here certainly isn't it.

HttpApplicationException: ...when there is an error processing an HTTP request

Well, we're running an ASP.NET application! But we're also just pulling at straws here.

InvalidOperationException: ...when a call is invalid for the current state of an instance

This isn't right but I'm adding it to the list of 'possible should you put a gun to my head, yes'.

OperationCanceledException: ...upon cancellation of an operation the thread was executing

Maybe I wouldn't feel so bad using this one, but I'd still be hijacking the damn thing with little right.

You might even ask why on earth I would want to raise an exception here but the idea is to find out that if I were to do so then do you know of an appropriate exception for such a scenario? And basically, to what extent can we piggy-back on .NET while keeping in line with rationality?


It seems like you're trying to deal with a situation where an error occurs that you simply can't recover from. When I hit these situations I do my best to fail and fail quickly. I typically avoid re-using a known exception here because this is not a situation I want to recover from. I want it to fail.

The way I generally approach this problem is through the use of Contracts.

private void Application_Start(object sender, EventArgs e)
{
  // If this doesn't work why keep executing???
  Contract.Requires(SetupHelper.SetUp());
  ...
}

Note: Depending on the project restrictions I will either use .Net Contracts from the BCL or my own variant like so

public static class Contracts {
  private sealed class ContractException : Exception { ... }
  public static void Requires(bool value) { 
    if (!value) {
      throw new ContractException(); 
    }
  }
}

My custom solution is inferior to BCL contracts but gets the job mostly done when BCL contracts aren't able to be used for whatever reason.


I think the problem in picking the exception type for your sample code lies in the fact that a boolean result from the .SetUp method gives you exactly zero information as to what happened.

I would do one of two things. The first thing would be to modify the .SetUp method to return something that gave me an indication of why it failed

Another route would be to have the .SetUp method itself throw the exception and decide in your Application_Start event whether to let it bubble up to the OS or fix whatever the problem is and try again.

Basically, when using exceptions you want the code that throws it to be as close as possible to the situation in order to intelligently alert the call stack of what the actual problem is.

If for some reason you can't do this, meaning something has failed and you have zero idea why then I'd create a new exception with a default message of: "An error occurred in {0}" where {0} is the module or method to at least give me an idea of where to start looking.

Incidentally, fatal usually means a total CLR failure, which is a bit different from your app being unable to process based on it's state.


I find that rolling my own exceptions gives me more control in handling issues.

private void Application_Start(object sender, EventArgs e)
{
    try
    {
        SetupHelper.SetUp()
        //Awesome
    }
    catch(InvalidDisplaySettingsException ex)
    {
        //Do Something based on the type of the exception
    }
    catch(DatabaseConfigurationException ex)
    {
        //Do something based on the type of the exception
    }
    catch(Exception ex)
    {
        //Now sh.. hit the fan
    }
}

.NET's built in exceptions are lacking most of the time. There are very few generic exceptions and the rest are way to specific to what they were built for that the standard programmer won't want to use them in his/her application. I like building my own exceptions because I can add extra information and to be able to do multiple catches.


Is this just a matter of cleanliness or do you have a reporting infrastructure that you want to be on the lookout for a specific exception? Obviously if you aren't planning on catching the exception then its type (practically speaking) matters not at all.

Depending why ShitHitTheFan then I would argue that InvalidOperationException or ApplicationException make sufficient sense. Adding a detailed message would seem sufficient to explain the cause. I tend to be pragmatic so the fact that the documentation indicates that ApplicationExceptions aren't fatal doesn't matter at all to me. Any exception can be fatal if it isn't caught...

Depending on any crash reporting/logging framework you have in place it may also be useful to fill out the Exception.Data property with state information that would help you in debugging the problem.

Edit: Also, if you do go the custom exception route - please, please implement the constructors necessary for serialization. You never know, months/years down the road, when your exception may be sent over the wire.


You need to read these articles on MSDN:

  • Catching and Throwing Standard Exception Types
  • Best Practices For Handling Exceptions

For your example code, throwing an InvalidOperationException is the most appropriate: if initialization failed, you most certainly are in an 'invalid state'.

You should not throw instances of SystemException or ApplicationException: in the beginning the design conceit was a hierarchy with System.Exception at the top of the heap. It has two children, SystemException and ApplicationException from which all other exceptions would derive.

SystemException was to be the root of system (e.g. CLR) exceptions, and ApplicationException the root of user/application defined exceptions. They figured out that in practice this provided no particular benefit: from the Best Practices article above:

For most applications, derive custom exceptions from the Exception class. It was originally thought that custom exceptions should derive from the ApplicationException class; however in practice this has not been found to add significant value.

If you are going to write custom exceptions, the need to be properly implemented. If there is a possibility that the exception will be thrown across application domain boundaries (e.g., cross-process or cross-machine), consider bundling your custom exception(s) into a standalone assembly referenced on both sides of the fence. You'll get...interesting...problems when an exception of unknown type is caught.

In implementing your custom exception, you'll need to provide, at a minimum, the 3 standard public constructors, plus the protected constructor and method required for serialization/deserialization, although the serialization-related constructor and method may be omitted if you've not actually extended the base class. Here's an example of the minimal scaffolding required for a robust custom exception:

using System;
using System.Runtime.Serialization;
using System.Security.Permissions;

namespace sandbox
{
    /// <summary>
    ///   <para>
    ///     Minimum scaffolding required for a custom exception.
    ///   </para>
    ///   <para>
    ///     Consider providing custom properties/fields to provide context and semantic details
    ///     regarding the exception instance. Alternatively, use the dictionary provided by the
    ///     base class for that purpose. See <see cref="System.Exception.Data"/> for more details.
    ///   </para>
    /// </summary>
    [Serializable]
    class MyCustomException : Exception
    {

        #region public constructors

        public MyCustomException() : base()
        {
            // set Message to the default localized message for your exception here
            // The base constructor for System.Exception sets InnerException to null as well as setting a default message.
            return ;
        }

        public MyCustomException( string message ) : base( message )
        {
            return ;
        }

        public MyCustomException( string message , Exception innerException ) : base( message , innerException )
        {
            return ;
        }

        #endregion public constructors

        #region serialization-related constructor/method

        /// <summary>
        /// protected constructor. Used for deserialization
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        protected MyCustomException( SerializationInfo info , StreamingContext context ) : base( info , context )
        {
            // do other deserialization-related initialization here (e.g. set your object properties from info and context).
            return ;
        }

        /// <summary>
        /// Perform custom serialization
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        [SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter=true)]
        public override void GetObjectData( SerializationInfo info, StreamingContext context ) 
        {
            // do any serialization work required by your custom exception here
            base.GetObjectData( info, context );
            return ;
        }

        #endregion serialization-related constructor/method

    }
}


Invalidoperationexception seems to be a good fit since It's intended purpose is to alert of something that went wrong and can't be recovered from.


I would consider most of the .net exceptions to be relatively useless for throwing from in any circumstance where you might expect them to be caught in anything other than a catch-all situation. The problem is that even if the state of your object might be known in all cases where you throw a TimeoutException, that doesn't mean that the state of your object will be known if some code you're calling throws a TimeoutException and you're just letting it bubble up the stack.

I would suggest that it's better to have more different types of exceptions than to use one exception type for multiple different purposes. If having to copy out the exception template gets annoying, here's a trick: define an exception type with one or more generic parameters. Note that a MisterDissapointedException<DerivedType> will not inherit from a MisterDissapointedException<CorrespondingBaseType>, but that you could define a MisterDissapointedException<T,U> that would inherit from MisterDissapointedException<T>.

If you go that route, then it won't be necessary to copy the template for each new exception you want. Simply pass in a class parameter that isn't used for any other MisterDissapointedException.


I would throw InvalidOperationException. Since this exception is not meant to be caught you don't need to worry about the type, the message is what's important. I would also throw it in the SetupUp method, instead of returning Boolean.


Your problem here is that the Setup method is using a return value instead of an Exception to report failure.

If you just let an exception bubble up from the implementation of Setup, it will be easier to see what type of exception should be thrown, e.g.

  • ConfigurationErrorsException if setup failed because of missing or invalid configuration information in your application configuration file.

  • SqlException if setup failed while accessing a SQL Server database during setup.

  • ... etc...

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜