35

I have a custom exception class which contains some additional fields. I want these to be written out in the ToString() method, but if I implement my own ToString(), I loose some other useful stuff (like writing the exception type name, the inner exception data and the stack trace).

What is the best way/pattern to implement your own ToString() method for such exceptions? Ideally it should reuse the existing mechanism, but be formatted in way similar to the default ToString() implementation.

UPDATE: prepending or appending my custom fields to the base.ToString() text isn't ideal IMHO, for example

PimTool.Utilities.OERestServiceUnavailableException: test ---> System.InvalidOperationException: inner message
   --- End of inner exception stack trace ---
   at PimTool.Tests.Services.OE.OERestClientTests.ExceptionsLogging() in D:\svn\NewPimTool\PimTool.Tests\Services\OE\OERestClientTests.cs:line 178, 
   StatusCode=0, message='test', requestId='535345'

means the custom fields are written at the end of the (potentially long) exception description. On the other hand, I want the exception type to be the first information written in the description.

UPDATE 2: I've implemented a solution for this, look for my own answer below.

Konamiman
  • 49,681
  • 17
  • 108
  • 138
Igor Brejc
  • 18,714
  • 13
  • 76
  • 95
  • I'm looking for the same thing, I want to add some extra information outside not in the Message field, but I want the ToString format to be kept, with all the stack trace and inner exceptions on it. It would be interesting to see the code for Exception.ToString() and copy it adding more info. – pauloya Jan 14 '10 at 16:52
  • Well the code covers most of the things you mentioned. Although I later remembered that I could've used Exception.Data dictionary to store my custom stuff. I guess in that case this would be rendered with the default implementation of ToString(), but I haven't tried it yet. – Igor Brejc Jan 15 '10 at 05:57
  • Related post - [Why / when would it be appropriate to override ToString?](https://stackoverflow.com/q/10278049/465053) – RBT Sep 06 '18 at 06:17

8 Answers8

42

This is all overkill. Your exception should just override the Message Property.

public override String Message {
    get {  
        return base.Message + String.Format(", HttpStatusCode={0}, RequestId='{1}'", 
                    httpStatusCode, 
                    RequestId);
    }
}

The default ToString method for the Exception class is basically "ClassName: Message --> InnerException.ToString() StackTrace". So overriding the Message puts your message text exactly where it should be.

Daryl
  • 18,592
  • 9
  • 78
  • 145
  • That works unless you're going down this road in order to inject a custom stack trace. – yoyo Oct 17 '15 at 05:40
  • @yoyo, yes but that wasn't a requirement of the OP – Daryl Oct 17 '15 at 12:04
  • Totally true, and your answer is a good one. (I came here looking for something a little different.) – yoyo Oct 17 '15 at 14:58
  • 2
    This works well, but I'm not sure it's great style, at least maybe not always, because the `Message` property isn't limited to usage by `ToString`. It may be unexpected for `new CustomException("My message").Message` to return a string `!= "My message"`. That could possibly bite someone if they want to display/log their message somewhere and not the additional info and assume `Message` is equal to the message they set. Not saying it's wrong, but something to consider case-by-case. – xr280xr Mar 03 '21 at 01:28
16

OK, this is what I came up with. I've implemented an extension class which replicates the original mechanism for formatting exceptions, but with a twist: a custom Action delegate which provides a plug-in for formatting custom fields:

public static class ExceptionFormatterExtensions
{
    public static string ExceptionToString (
        this Exception ex, 
        Action<StringBuilder> customFieldsFormatterAction)
    {
        StringBuilder description = new StringBuilder();
        description.AppendFormat("{0}: {1}", ex.GetType().Name, ex.Message);

        if (customFieldsFormatterAction != null)
            customFieldsFormatterAction(description);

        if (ex.InnerException != null)
        {
            description.AppendFormat(" ---> {0}", ex.InnerException);
            description.AppendFormat(
                "{0}   --- End of inner exception stack trace ---{0}",
                Environment.NewLine);
        }

        description.Append(ex.StackTrace);

        return description.ToString();
    }
}

Now you can use this method in your own ToString() implementations without duplicating the formatting code:

    public override string ToString()
    {
        return this.ExceptionToString(
            description =>
            {
                description.AppendFormat(
                    ", HttpStatusCode={0}, RequestId='{1}'", 
                    httpStatusCode, 
                    RequestId);
            });
    }
Igor Brejc
  • 18,714
  • 13
  • 76
  • 95
14

You could manually add the default data to the string returned by ToString, by looking at the exception properties. For example, the following will simulate the data returned by default by a exception's ToString method (assuming there are no inner exceptions):

string.Format("{0}: {1}\r\n{2}", this.GetType().Name, this.Message, this.StackTrace);

Or, you could simply append (or prepend) the data returned by base.ToString to the information you want to add.

Konamiman
  • 49,681
  • 17
  • 108
  • 138
  • 2
    This seems to be the closest solution to what I had in mind. Although you forgot to write out the InnerException ;) – Igor Brejc Dec 11 '09 at 08:39
12

You can override the ToString() method to include your own custom information, and still call the default base Exception ToString() like this:

public class MyException : Exception
{
    public string CustomField { get; set; }
    public override string ToString()
    {
        return CustomField + Environment.NewLine + base.ToString();
    }
}
Chris Fulstow
  • 41,170
  • 10
  • 86
  • 110
5

If you're primarily looking at them in the debugger, then you can use the [DebuggerDisplay] attribute to specify their formatting and not touch the existing ToString method.

Otherwise, just overload ToString and be sure to call the base class version base.ToString()

Andrew Rollings
  • 14,340
  • 7
  • 51
  • 50
2

The simplest way

public override string ToString()
{
    StringBuilder sb = new();
    var separator = new string[] { Environment.NewLine };
    var str = base.ToString().Split(separator, 2, StringSplitOptions.None);
    sb.AppendLine(str[0]);

    // Your properties

    sb.Append(str[1]);
    return sb.ToString();
}
Glinko
  • 21
  • 1
1

Inside the override call base.ToString() and modify the resulting string to your needs...

Fried Hoeben
  • 3,247
  • 16
  • 14
1

Similar idea to Glinko's answer of insertion of your variables after the first line of base string, a bit different implementation:

public override string ToString()
{
    string baseStr = base.ToString();
    return baseStr.Insert(baseStr.IndexOf('\n') + 1,
        string.Format("HttpStatusCode: {0}, TSError400: {1}\r\n",
        (int)HttpStatusCode,
        JsonConvert.SerializeObject(TSError400)));
}
Vasiliy Zverev
  • 622
  • 5
  • 10