views:

71

answers:

3

I have been thinking of a good generic exception object that would replace throw new Exception(string.Format("...",...)), to both simplify and also to speed up such objects. The formatting by slow String.Format() should be delayed until Message property is called. Serialization is also somewhat risky. Also, such object could later implement localization.

Update: This exception should be inherited by more specific user exceptions, not thrown itself. Sorry for not making this clear.

This is what I have come up with. Please comment if there are any ways to improve it. Thanks!

/// <summary>
/// Generic exception capable of delayed message formatting.
/// Inherit for more specific exceptions.
/// </summary>
[Serializable]
public class FormattedException : Exception
{
    private readonly object[] _arguments;
    private readonly string _formatStr;
    private readonly bool _useFormat;

    private FormattedException(bool useFormat, Exception inner, string message, params object[] args)
        : base(message, inner)
    {
        _useFormat = useFormat;
        _formatStr = message;
        _arguments = args;
    }

    public FormattedException()
        : this(false, null, null, null)
    {}

    public FormattedException(string message)
        : this(false, null, message, null)
    {}

    public FormattedException(string message, params object[] args)
        : this(true, null, message, args)
    {}

    public FormattedException(Exception inner, string message)
        : this(false, inner, message, null)
    {}

    public FormattedException(Exception inner, string message, params object[] args)
        : this(true, inner, message, args)
    {}

    public override string Message
    {
        get
        {
            if (!_useFormat)
                return _formatStr;

            try
            {
                return string.Format(_formatStr, _arguments);
            }
            catch (Exception ex)
            {
                var sb = new StringBuilder();

                sb.Append("Error formatting exception: ");
                sb.Append(ex.Message);
                sb.Append("\nFormat string: ");
                sb.Append(_formatStr);
                if (_arguments != null && _arguments.Length > 0)
                {
                    sb.Append("\nArguments: ");
                    for (int i = 0; i < _arguments.Length; i++)
                    {
                        if (i > 0) sb.Append(", ");
                        try
                        {
                            sb.Append(_arguments[i]);
                        }
                        catch (Exception ex2)
                        {
                            sb.AppendFormat("(Argument #{0} cannot be shown: {1})", i, ex2.Message);
                        }
                    }
                }

                return sb.ToString();
            }
        }
    }

    #region Serialization

    private const string SerializationField = "FormatString";

    protected FormattedException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
        _formatStr = (string) info.GetValue(SerializationField, typeof (string));
        // Leave other values at their default
    }

    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
        // To avoid any serialization issues with param objects, format message now
        info.AddValue(SerializationField, Message, typeof (string));
    }

    #endregion
}
A: 

Shouldn't you serialize the arguments as well?

chris166
No, because objects passed as arguments may not be serializable or may not exist on the other side (remoting). It is much safer to serialize the string than to deal with deserialization problems.
Yurik
+3  A: 

It's an interesting thought, but not a good idea. The reason to create a custom exception have nothing to do with ease of use - only create a custom exception if someone is going to catch that exception type and do something different with it.

Instead of a custom exception, maybe you can create an extension method.

John Saunders
This is just a base class for other user exceptions! I was even considering making it abstract but decided against it because System.Exception is also a public class.Extension methods have a number of limitations: string.Format() will happen right away, causing an unneeded delay, not easy to throw multiple types.If you look at how ArgumentOutOfRangeException is done, it delays formatting until Message property is called.
Yurik
A: 

Exception handling strategies are based on exception types not exception messages. If you want user to handle your exception you should add meaningful semantic for it. But I truly don't understand how I can handle your exception (expept to show it for the user).

And I think it not a good idea to compute message inside Message property (multiple access to it may cause significant performance drawback). And all your property should behave like fields (here I talked a bit about it).

Sergey Teplyakov
Agree - this was designed as a base class for more specific exceptions, not to be thrown by itself. As for computing - Message property will be called much less frequently than exception throwing/catching, as only logging / user displaying systems will look at it. I didn't want to deal with the multithreading issues of caching the result.
Yurik
Hmmm. And what can stop your derive classes from overriding your Message property? I think in this case you should use aggregation, not inheritance.
Sergey Teplyakov