I find the default Exception.ToString() formattig very difficult to read especially if there are inner exceptions involved. I guess everyone knows how it looks:System.ArgumentException: This is a message. ---> UserQuery+TestException: You need to specify a valid id.   at UserQuery.foo(Int32 id) in C:\[...]\LINQPad5\_vheqmalf\query_bkavzy.cs:line 56   at UserQuery.Main() in C:\[...]\LINQPad5\_vheqmalf\query_bkavzy.cs:line 37   --- End of inner exception stack trace ---   at UserQuery.Main() in C:\[...]\LINQPad5\_vheqmalf\query_bkavzy.cs:line 41So I thought I create my own formatting and later a base exception to get a better result - at least one that is more pleasing my eyes ;-)TestException  You need to specify a valid id.{   ExtraInfo:   [      Bar: Baz   ]   StackTrace:   [      System.String UserQuery.foo(System.Int32 id)  C:\[...]\LINQPad5\_vheqmalf\query_bkavzy.cs:56      System.Void UserQuery.Main()  C:\[...]\LINQPad5\_vheqmalf\query_bkavzy.cs:37   ]}ArgumentException  This is a message.{   ExtraInfo:   [      ParamName:    ]   StackTrace:   [      System.Void UserQuery.Main()  C:\[...]\LINQPad5\_vheqmalf\query_bkavzy.cs:41   ]}It's similar to json but It shouldn't be json. It should be easy to read. I'm not going to parse it.To keep the implementation short I chose to use mainly dynamic objects for the formatter. It has two methods:Format that takes an exception and turns it into as stringGetExceptionInfos that extracts the relevant data from each exceptionExceptionStringFormatter class:internal class ExceptionStringFormatter : IExceptionFormatter{    private const string Space =  ;    private const string CurlyBracketLeft = {;    private const string CurlyBracketRight = };    private const string SquareBracketLeft = [;    private const string SquareBracketRight = ];    private const string RaquoSpacer =   ;    private const int IndentWidth = 3;    public string Format<TException>(TException exception)        where TException : Exception    {        var exceptionInfos = GetExceptionInfos(exception).Reverse();        var exceptionString = new StringBuilder();        foreach (dynamic exceptionInfo in exceptionInfos)        {            exceptionString                .Append(exceptionInfo.ExceptionType.Name).Append(RaquoSpacer)                .Append(exceptionInfo.ExceptionMessage ?? <Message>)                .AppendLine();            exceptionString.Append(CurlyBracketLeft).AppendLine();            // format properties            exceptionString.Append(Indent(1)).Append(ExtraInfo:).AppendLine();            exceptionString.Append(Indent(1)).Append(SquareBracketLeft).AppendLine();            foreach (var property in (IDictionary<string, object>)exceptionInfo.CustomProperties)            {                exceptionString                    .Append(Indent(2))                    .Append(property.Key + : )                    .Append(\ + property.Value + \)                    .AppendLine();            }            exceptionString.Append(Indent(1)).Append(SquareBracketRight).AppendLine();            // format stack trace            exceptionString.Append(Indent(1)).Append(StackTrace:).AppendLine();            exceptionString.Append(Indent(1)).Append(SquareBracketLeft).AppendLine();            foreach (var stackFrame in exceptionInfo.StackTrace)            {                exceptionString                    .Append(Indent(2))                    .Append(stackFrame.CallerSignature).Append(RaquoSpacer)                    .Append(stackFrame.FileName)                    .Append(: + stackFrame.LineNumber)                    .AppendLine();            }            exceptionString.Append(Indent(1)).Append(SquareBracketRight).AppendLine();            exceptionString.Append(CurlyBracketRight).AppendLine();        }        exceptionString.ToString().Dump();        return string.Empty;    }    internal static IEnumerable<dynamic> GetExceptionInfos<TException>(TException exception)        where TException : Exception    {        foreach (var ex in exception.AsEnumerable())        {            var exceptionInfo = new ExpandoObject() as dynamic;            exceptionInfo.ExceptionType = ex.GetType();            exceptionInfo.ExceptionMessage = ex.Message;            exceptionInfo.CustomProperties = new ExpandoObject() as dynamic;            foreach (var property in ex.GetCustomProperties())            {                ((IDictionary<string, object>)exceptionInfo.CustomProperties)[property.Key] = property.Value;            }            var strackTrace = new StackTrace(ex, true);            var stackFrames = strackTrace.GetFrames();            exceptionInfo.StackTrace = stackFrames?.Select(sf =>            {                var stackFrame = new ExpandoObject() as dynamic;                stackFrame.CallerSignature = sf.GetMethod().Format();                stackFrame.FileName = sf.GetFileName();                stackFrame.LineNumber = sf.GetFileLineNumber();                return stackFrame;            }).ToList();            yield return exceptionInfo;        }    }    private static string Indent(int depth)    {        return string.Empty.PadLeft(IndentWidth * depth);    }}There are also two helper classes.One is the ExceptionExtensions. Its purpose is to enumerate the inner exceptions and to get the custom properties from each one.internal static class ExceptionExtensions{    private static readonly HashSet<string> ExceptionPropertyNames = new HashSet<string>(        typeof(Exception)        .GetProperties(BindingFlags.Instance | BindingFlags.Public)        .Select(p => p.Name));    public static IDictionary<string, object> GetCustomProperties<TException>(this TException exception)         where TException : Exception    {        var currentExceptionProperties =            exception.GetType()            .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)            .Where(p => !ExceptionPropertyNames.Contains(p.Name));        var result = currentExceptionProperties.ToDictionary(property =>             property.Name,             property => property.GetValue(exception));        return result;    }    public static IEnumerable<Exception> AsEnumerable(this Exception exception)    {        var currentException = exception;        do        {            yield return currentException;            currentException = currentException.InnerException;        } while (currentException != null);    }}and the other one is the MethodSignatureFormatter that creates method signature from method-info:internal static class MethodSignatureFormatter{    public static string Format(this MethodBase method)    {        var methodInfo = method as MethodInfo;        var signature =            new StringBuilder()            .Append(methodInfo.ReturnType.FullName)            .Append( ).Append(methodInfo.DeclaringType.FullName)            .Append(.).Append(methodInfo.Name)            .Append(()            .Append(string.Join(, , methodInfo.GetParameters().Select(p =>                 p.ParameterType.FullName +   + p.Name)))            .Append());        return signature.ToString();    }}Base exception with custom formatting:public abstract class FormattableException : Exception{    protected FormattableException(string message, Exception innerException)        : base(message, innerException) { }    protected FormattableException(string message) : base(message) { }    public IExceptionFormatter Formatter { get; set; } = new ExceptionStringFormatter();    public override string ToString()    {        return Formatter?.Format(this) ?? base.ToString();    }}Demo:void Main(){    try    {        try        {            foo(9);        }        catch (Exception ex)        {            throw new ArgumentException(This is a message., ex);        }    }    catch (Exception ex)    {        Console.WriteLine(ex.ToString());        Console.WriteLine(-);        Console.WriteLine(new ExceptionStringFormatter().Format(ex));    }}string foo(int id){    throw new TestException(You need to specify a valid id.) { Bar = Baz };}
Custom Exception.ToString() formatter
c#;strings;formatting;reflection;exception
private const string Space =  ;There's little point in storing this in a variable: it's never going to change anyway and you're not clarifying any of the intent. I'm fully aware that  is a space and { is a left curly bracket. Using dynamic is a very, very expensive action. By defaulting to it everywhere instead of using an intermediate type you're incurring heavy costs on yourself..Append(exceptionInfo.ExceptionMessage ?? <Message>)I'm not sure if <Message> is a default I'd like to see. It makes it feel as if something wasn't parsed correctly -- perhaps you can just leave it empty or give it an actual description like No message found?internal class ExceptionStringFormatter : IExceptionFormatterIt has no instance fields so you might as well make it a static class with static members. That way you don't have to create a new object every time you want to format an exception string.exceptionString.ToString().Dump();return string.Empty;Testing remnant?var exceptionInfo = new ExpandoObject() as dynamic;Might as well just writedynamic exceptionInfo = new ExpandoObject();AsEnumerable(this Exception exception)Seems like a questionable name for what it does. Perhaps something like GetInnerExceptions() is more appropriate considering that's what it is actually implemented as and not just a projection of one exception to a list of exceptions.var methodInfo = method as MethodInfo;Use a direct cast or perform a null check. Right now you're just using the result directly which could mean you get a NullReferenceException -- a ClassCastException would be much more helpful in identifying the issue.