4

Faced with code that make one exception instance and then probably throw it multiple times.

private readonly Exception exceptionInstance = new Exception("message");

Is it correct to throw same exception instance multiple times?

gabba
  • 2,815
  • 2
  • 27
  • 48
  • 2
    Out of curiosity, why do you want to only use a specific instance? – Crowcoder Mar 28 '18 at 12:21
  • 1
    You mean `throw exceptionInstance` from your code? An exception is sually just a data-object. You could use the same instance again and again, but I can´t see any reasn to do so, as it will allways contain the exact same message and thus won´t have a very generic use. – MakePeaceGreatAgain Mar 28 '18 at 12:21
  • Singleton? But why? – P.Brian.Mackey Mar 28 '18 at 12:25
  • @Crowcoder, I don't know why author do this. My question is about possible issues of this code. – gabba Mar 28 '18 at 12:33
  • As with any mutable state, the first possible issue I see is that the message may be changed without some other code "knowing" about it. That will lead to misleading exceptions. – Crowcoder Mar 28 '18 at 12:35
  • @P.Brian.Mackey, I don't know why, but want to know should I refactor this code for any reason? – gabba Mar 28 '18 at 12:36
  • 1
    You should certainly refactor this code. For example I'm quite sure that this is not thread-safe, so when you will throw this exception from multiple threads - who knows what can happen. – Evk Mar 28 '18 at 12:39
  • @Crowcoder, I think in that case this is not problem, because this object stored in private field, so only local code could do this – gabba Mar 28 '18 at 12:41
  • @Evk, good point. Internal state of exception could be changed when exception travel by stack. So we can have two parallel conflicting throws – gabba Mar 28 '18 at 12:44
  • In general, you should re-use the same exception to preserve the stack trace when using `throw`. That's how you use "the same instance". Rethrowing the same exception – P.Brian.Mackey Mar 28 '18 at 13:40

2 Answers2

5

It's bad practice for various already stated reasons, but it will fail especially hard in multithreaded code, because Exception class is (obviously) not thread safe, and it's not immutable.

Consider this code:

class Program {
    static readonly Exception _test = new Exception("test");

    static void Main(string[] args) {
        ThreadPool.SetMinThreads(10, 8);
        var random = new Random();
        int num1 = 0;
        int num2 = 0;
        var tasks = new List<Task>();
        for (int i = 0; i < 10; i++) {
            tasks.Add(Task.Run(() => {
                try {
                    if (random.Next(0, 2) == 0) {
                        Interlocked.Increment(ref num1);
                        Throw1();
                    }
                    else {
                        Interlocked.Increment(ref num2);
                        Throw2();
                    }
                }
                catch (Exception ex) {
                    Console.WriteLine(ex);
                }
            }));
        }

        Task.WaitAll(tasks.ToArray());
        Console.WriteLine("num1: " + num1);
        Console.WriteLine("num2: " + num2);
        Console.ReadKey();
    }

    static void Throw1() {
        throw _test;
    }

    static void Throw2() {
        throw _test;
    }
}

Here we have 2 methods, Throw1() and Throw2() which both throw the same instance of exception from private field. Then we run 10 threads which call either Throw1() or Throw2() randomly and print what has been thrown. Example output of such code is:

System.Exception: test
   в ConsoleApp8.Program.Throw1() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 52
   в ConsoleApp8.Program.<>c__DisplayClass1_0.<Main>b__0() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 32
System.Exception: test
   в ConsoleApp8.Program.Throw1() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 52
   в ConsoleApp8.Program.<>c__DisplayClass1_0.<Main>b__0() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 32
System.Exception: test
   в ConsoleApp8.Program.Throw1() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 52
   в ConsoleApp8.Program.<>c__DisplayClass1_0.<Main>b__0() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 32
System.Exception: test
   в ConsoleApp8.Program.Throw1() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 52
   в ConsoleApp8.Program.<>c__DisplayClass1_0.<Main>b__0() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 32
System.Exception: test
   в ConsoleApp8.Program.Throw1() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 52
   в ConsoleApp8.Program.<>c__DisplayClass1_0.<Main>b__0() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 32
System.Exception: test
   в ConsoleApp8.Program.Throw1() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 52
   в ConsoleApp8.Program.<>c__DisplayClass1_0.<Main>b__0() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 32
System.Exception: test
   в ConsoleApp8.Program.Throw1() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 52
   в ConsoleApp8.Program.<>c__DisplayClass1_0.<Main>b__0() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 32
System.Exception: test
   в ConsoleApp8.Program.Throw1() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 52
   в ConsoleApp8.Program.<>c__DisplayClass1_0.<Main>b__0() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 32
System.Exception: test
   в ConsoleApp8.Program.Throw1() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 52
   в ConsoleApp8.Program.<>c__DisplayClass1_0.<Main>b__0() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 32
System.Exception: test
   в ConsoleApp8.Program.Throw1() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 52
   в ConsoleApp8.Program.<>c__DisplayClass1_0.<Main>b__0() в H:\VSProjects\SoHelp\ConsoleApp8\Program.cs:строка 32
num1: 6
num2: 4

So while Throw1() has been called 6 times and Throw2() has been called 4 times - all 10 stack traces we printed refer to Throw1() method.

So just don't ever do that, because there is absolutely no reasons to.

Evk
  • 98,527
  • 8
  • 141
  • 191
  • Readonly fields are immutable. Statics are guaranteed to be initialized prior to being called. It's just whichever thread happens to initialize the static wins. The state never changes after that. – P.Brian.Mackey Mar 28 '18 at 13:23
  • 1
    @P.Brian.Mackey I don't agree, because if you try to invoke `Throw1()` and `Throw2()` from my example sequentially (from the same thread) - `catch` will print 2 different stack traces, even though exception is same. And Exception has many mutable fields, one example is `Message` itself, but there are much more. – Evk Mar 28 '18 at 13:26
  • That's likely a side effect of the test using `throw _test`. By definition this generates a new exception. I don't know if throw is designed to work this way. However, we do have a guarantee that static ctor's are designed to be called 1x even across threads. That's why I'm confident this isn't an issue of immuteability. More likely an issue with the thread safety of throwing new exceptions. – P.Brian.Mackey Mar 28 '18 at 13:38
  • 1
    @P.Brian.Mackey I don't see how it's related to static constructors at all. `_test` exception is created once and thrown multiple times, then various calls mutate its internal state. And no - throw does not create new exception, which you can verify by comparing exception caught in `catch` with `_test`, using `object.ReferenceEquals`. And what you call a "side effect" is basically what this question is about. – Evk Mar 28 '18 at 13:42
  • Sorry, I neglected to mention that I moved the field initialization into the static ctor and still saw the mis-behavior you described. And yes you are right about `throw ex`. Hm. This makes me question some of the material I've read on statics. – P.Brian.Mackey Mar 28 '18 at 13:47
  • 1
    @P.Brian.Mackey note that question is not about rethrowing caught exception - that's perfectly fine of course. Question is about storing one exception instance in a field and then throw it from multiple completely unrelated places, instead of just 'throw new Exception("test")'. This one is bad. – Evk Mar 28 '18 at 13:48
  • The part I still don't understand about this answer is how to reconcile my understanding of statics: https://stackoverflow.com/questions/4506990/what-is-the-use-of-static-constructors with what we see here. Move the exception into a static ctor and you will see the same non-thread safe behavior happening. – P.Brian.Mackey Mar 29 '18 at 13:57
  • @P.Brian.Mackey I don't see how static constructor is related here. By "event" you mean `Exception _test`? – Evk Mar 29 '18 at 14:01
  • Yes, I corrected the comment. I also explained how I modified your example to include static ctor's. I would expect static field initialization and ctor init to behave the same. However, to be safe I moved the initialization into the ctor and this code still behaves in a non-thread safe way. That doesn't make sense considering the link I gave you. – P.Brian.Mackey Mar 29 '18 at 14:11
  • 1
    @P.Brian.Mackey field initialization is already kind of in static constructor. Static field initializers behave the same way. Anyway, problem is not how `Exception` instance is created. It's created only once, on one thread. Problem is it's being modified later, from multiple threads. When you do `Console.WriteLine(ex)` for example, this is the same as `Console.WriteLine(ex.ToString())`. `ToString()` of `Exception` calls various methods, and those methods _modify private fields on exception_. This is not thread safe. – Evk Mar 29 '18 at 14:15
  • 1
    @P.Brian.Mackey for example, `ToString()` calls `GetClassName` private method, which does roughly this: `if (_className == null) _className = GetClassNameIntl(); return _className`. This way, innocent call to `ToString()` modifies private fields of `Exception`. And we are doing this from multiple threads in this case, which leads to observed behavior. All that because `Exception` is not thread-safe, and it should not be, because you should not use it like this. – Evk Mar 29 '18 at 14:17
3

No. Throwing the same instance of Exception from multiple places in your code (not counting rethrows!) is not recommended.

An Exception contains more then just it's message - it contains valuable information for debugging such as the stack trace. (Update: just tested that now. the stack trace is probably added to the exception in the throw statement, so it's not relevant to this answer) and TargetSite (from my tests, seems like it is being populated the first time the exception is thrown, but never again after that).

Throwing the same instance of Exception in different places in your code would deny you the ability to use some of this data.

Zohar Peled
  • 79,642
  • 10
  • 69
  • 121
  • Stack trace will actually be different. If you throw that same exception from different methods - you will see in `catch` block that stack trace (and properties like TargetSite) changes every time. Not that I argue that this is a bad practice though... – Evk Mar 28 '18 at 12:34
  • @Evk Yes, just tested that now. the stack trace is probably added to the exception in the throw statement – Zohar Peled Mar 28 '18 at 12:35