Exceptions in Dynamics Ax are quite different than exceptions in .Net. Not only because of the type “System.Exception” and the way you are handling exceptions, but because of the different philosophy on handling exceptions, too. First of all I’d like to quote Jeffrey Richter’s (CLR in C#) definition of an exception:
When an action member cannot complete its task, the member should throw an exception. An exception means that an action member failed to complete the task it was supposed to perform as indicated by its name.
So whenever anything happens, that might prevent to execute the code as you intended to do it, and that is not consistent to the name of your method – it’s an exception.
you note that the definition of an exception in the official development documentation of Dynamics Ax is slightly different:
An exception is a regulated jump away from the regular sequence of program instruction execution. The instruction at which program execution resumes is determined by try - catch blocks and the type of exception that is thrown.
In my point of view this definition is too technical and error-centric and does forget the main cause for an exception as mentioned by Jeff Richter.
[..]An exception means that an action member failed to complete the task it was supposed to perform as indicated by its name.
The X++ definition does not mention however what an exception really is and in which cases you use them. It simply tells you how to apply them and shows you the techniques.
I think I don't need to present you the basics such "try" and "catch" in this article. Many other blogs have done this much better than I would be able to do it...
2.1 Exception handling in X++
In X++ you have two different kind of exceptions that might be thrown: The application generated exception and the kernel generated exceptions. Technically an exception in X++ is an Enum and the type of exception is differentiated by it’s value. The Enum Exception contains the following types:
- Info
- Warning
- Deadlock
- Error
- Internal
- Break
- DDEerror
- Sequence
- Numeric
- CLRError
- CodeAccessSecurity
- UpdateConflict
- UpdateConflictNotRecovered
- DuplicateKeyException
- DuplicateKeyExceptionNotRecovered
2.1.1 Kernel generated exception
Kernel generated exceptions can only be thrown by the kernel. The following error-types are kernel-errors:
- Deadlock
- Internal
- Break
- DDEerror
- Sequence
- Numeric
- CLRError
- CodeAccessSecurity
- UpdateConflict
- UpdateConflictNotRecovered
- DuplicateKeyException
- DuplicateKeyExceptionNotRecovered
Have a look at all these types and their description, because they vary a lot and not knowing particular behaviors might cause you many troubles. You’ll find some descriptions here.
2.1.2 Application generated exception
The application generated exceptions are: Info, Warning and Error. These exceptions are meant to be thrown by the application code and are as follows:
- “Info”: just transmits useful infos
- “Warning”: indicated recoverable problems
- “Error”: unrecoverable problems.
The example hereunder will gives you an idea about these three types:
1: public static void main(Args arg)
2: {
3: //Info
4: try
5: {
6: throw Info("here we have an info");
7: }
8: catch(Exception::Info)
9: {
10: info("Info catched");
11: }
12: catch
13: {
14: info("If anything else should be catched...");
15: }
16: //Warning
17: try
18: {
19: throw Warning("Here's a warning");
20: }
21: catch(Exception::Info)
22: {
23:
24: info("catched a Warning");
25: }
26: catch
27: {
28: info("catched the rest");
29: }
30: //Error
31: try
32: {
33: throw Error("Here's an error happening");
34: }
35: catch(Exception::Error)
36: {
37: info("catched a thrown error");
38: }
39: catch
40: {
41: info("catched the rest");
42: }
43: }
Here is the screenshot of the executed code:
You’ll notice the different symbols for "warning" and "error". Please remember that in Dynamics Ax “Warnings” are indicating recoverable problems and “Errors” unrecoverable problems.
2.1.3 The retry statement
A rather useful statement in the X++ language is “retry”. Whenever you have a recoverable problem (such as Deadlocks or individual problems that you are able to resolve in a further execution) you can use the "retry" statement as follows:
1: public static void main(Args arg)
2: {
3: int i =0;
4: ;
5: try
6: {
7: i += 1;
8: //Verification that it does not execute until infinity
9: if (i > 10)
10: {
11: throw Error("Ok this should be enough");
12: }
13: throw Warning("Here's an error happening");
14: }
15: catch(Exception::Warning)
16: {
17: //do something to repair the problem
18: retry;
19: }
20: catch(Exception::Error)
21: {
22: Info("Here it ends");
23: }
24: }
The “retry” statement in line 19 will re-execute the code from the beginning of the catch-scope in line 7. This is extremely useful but has to be used with care. The problem in the following code might be easy to identify, but in practice it will take a lot of time to identify this kind of problems:
1: public static void main(Args arg)
2: {
3: int i;
4: ;
5: try
6: {
7: i =0;
8: i += 1;
9: //Verification that it does not execute until infinity
10: if (i > 10)
11: {
12: throw Error("Ok this should be enough");
13: }
14:
15: throw Warning("Here's an error happening");
16: }
17: catch(Exception::Warning)
18: {
19: //do something to repair the problem
20: retry;
21: }
22: catch(Exception::Error)
23: {
24: Info("Here it ends");
25: }
26: }
Because the retry will execute the whole catch-scope; it will in this case reinitialize the “i” and by consequence result in a infinite loop. For some errors such as “Deadlock” the use of retry might be appropriate, but in any case it is as risky as the “good old” “goto”-statement that we all know too well. I’ll come to the goto late, in the C# chapter.
2.1.4 The missing finally
Unfortunately we have in X++ no possibility to execute code after the try-block whether the code was successfully executed or not. Other languages (like C#) offers “finally” that prevents you writing redundant code: at the end of the code inside the catch and after all catch catches (in this example it would be after line25). I mentioned this problem in this posting. So just be sure that whatever you need to be executed, is executed in the catch and after that.
2.1.5 The “catch all” block
In most of the examples I was not adding the “catch all” block:
1: catch2: {
3: info("Caught an exception.");4: }
This last block will catch all exceptions that were not mentioned explicitly earlier and catch exception types for all exceptions that have not been already specified before. Catching all exceptions, even if you have no idea about the cause is a no do and and a typically anti-pattern. Maybe you have an idea of what to expect, but in most of the cases it’s something completely different. So be as precise as you can be and if you can’t, do leave traces. .Net offers you the possibility to bubble up the exception as is, so that no information is lost and you can trace whatever you need to trace. I’ll come back to it later in the C# chapter.
An article that goes more deeper in details is available on MSDN here.
2.1.5 The CLRError and other not displayed errors.
As you might have noticed in the X++ documentation:
Exception::CLRError, Exception::UpdateConflictNotRecovered, and system kernel exceptions are examples of exceptions that are not automatically displayed in the Infolog.
So both exception-types are not automatically displayed in the Infolog and you should do this manually by your own. Some month ago I blogged about handling CLRErrors and this might give you an idea how to get the real error out of the CLRError. Be careful that all of your errors are well logged! This is a frequent cause for unnecessarily loosing precious time. So take care of this when using CLR-objects with X++.
2.2 Exception handling in .Net
You'll find an article about comparing the X++ and the C# exception handling on Msdn here. But this is not the point of this article.
The theoretical definition of an exception has already been given by Jeffrey Richter in the first part of this posting, and I’m convinced that this is important enough to be worth repeating it:
-
When an action member cannot complete its task, the member should throw an exception. An exception means that an action member failed to complete the task it was supposed to perform as indicated by its name.
Technically an Exception in .Net is a type that derives always from “System.Exception” (at least since .Net 2.0). You’ll see that this time the Msdn-documentation is really exhaustive and it is absolutely worth being read by everyone developing .Net applications. Even if the CLR permits theoretically throwing any type (others than Exception-derived types like for example Strings), most of the .Net languages doesn’t support this and this isn’t CLS-compliant. Those non CLS-compliant exceptions are wrapped by the CLR in a RuntimeWrappedException, so that you might catch them in any language like C# which don’t support those kind of exceptions. This behavior has been introduced since .Net 2, and that’s why I mentioned it earlier…
2.2.1 The System.Exception
The System.Exception type has some public properties (here the link to Msdn) that are worth to be mentioned:
-Message: Contains technical information about the exception and helps the developer to understand what happened.
-Source: The name of the assembly that generated the exception. Especially in complex architectures this is very useful.
-StackTrace: This contains the stack that led to the exception. It contains all methods with it’s complete signature.
-TargetSite: The method that threw the exception
-InnerException: Contains the previous exception.
Exceptions can be caught as you were used to in X++ but additionally to that in many other ways, too. If you were to catch exceptions as you did in X++ you would probably loose most of the information that might help you find the cause of your problem easily. The X++-style would be the following:
1: try
2: {
3: throw new ApplicationException("a message");
4: }
5: catch (ApplicationException)
6: {
7: MessageBox.Show("an ApplicationException has been caught");
8: }
In line 5 the Exception will be caught and in line 7 something is done with this exception. But all the information that I wrote about above are lost. So here’s how to get them:
1: try
2: {
3: throw new ApplicationException("a message");
4: }
5: catch (ApplicationException aex)
6: {
7: MessageBox.Show(aex.Message);
8: }
In line 5 you catch the ApplicationException and assign it to the variable aex.
One of the most important rules of exception-handling in .Net is catching exceptions whenever you can handle that case. In most of the time the catch-statement are not useful and you should verify each time you are catching an exception if this gives you a real value. If this added value is tracing the issue, than you must rethrow this exception.
-
First code:
1: try
2: {
3: throw new ApplicationException("a message");
4: }
5: catch (ApplicationException aex)
6: {
7: System.Diagnostics.Trace.Write(aex);
8: throw;
9: }
It’s very important that you don’t throw the aex (throw aex;), since you would loose precious information about the exception and the exception will point to line 8, and the original Exception will be in the InnerException. The following example demonstrates you this and compare both results. But first the code:
-
Second code:
1: {
2: try
3: {
4: throw new ApplicationException("a message");
5: }
6: catch (ApplicationException aex)
7: {
8: System.Diagnostics.Trace.Write(aex);
9: throw aex;
10: }
Here we throw the aex exception instead of the original exception. Now let us have a look at the results:
The result of the first code is:
1: System.ApplicationException was unhandled
2: Message="a message"
3: Source="WinForm"
4: StackTrace:
5: at WinForm.Form1.button1_Click(Object sender, EventArgs e) in
6: C:/Users/floditt/Documents/Visual Studio 2008/Projects/WinForm/WinForm/Form1.cs:line 28 (line 28 corresponds to line 3 in the first code)
7: at System.Windows.Forms.Control.OnClick(EventArgs e)
8: at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
9: at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
10: at System.Windows.Forms.Control.WndProc(Message& m)
11: at System.Windows.Forms.ButtonBase.WndProc(Message& m)
12: at System.Windows.Forms.Button.WndProc(Message& m)
13: at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
14: at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
15: at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
16: at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
17: at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
18: at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
19: at WinForm.Program.Main() in C:/Users/floditt/Documents/Visual Studio 2008/Projects/WinForm/WinForm/Program.cs:line 18
20: at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
21: at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
22: at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
23: at System.Threading.ThreadHelper.ThreadStart()
24: InnerException:
In order to make the result easy to read I truncated line 5 and 6. The line 28 (you find in line 6) that throws the exception is exactly the location there the exception was thrown and corresponds to the line 3 in the first code.
Now we take the result of the second example:
1: System.ApplicationException was unhandled
2: Message="a message"
3: Source="WinForm"
4: StackTrace:
5: at WinForm.Form1.button2_Click(Object sender, EventArgs e) in
6: C:/Users/floditt/Documents/Visual Studio 2008/Projects/WinForm/WinForm/Form1.cs:line 41 (line 41 corresponds to line 9 in the first code)
7: at System.Windows.Forms.Control.OnClick(EventArgs e)
8: at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
9: at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
10: at System.Windows.Forms.Control.WndProc(Message& m)
11: at System.Windows.Forms.ButtonBase.WndProc(Message& m)
12: at System.Windows.Forms.Button.WndProc(Message& m)
13: at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
14: at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
15: at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
16: at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
17: at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
18: at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
19: at WinForm.Program.Main() in C:/Users/floditt/Documents/Visual Studio 2008/Projects/WinForm/WinForm/Program.cs:line 18
20: at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
21: at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
22: at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
23: at System.Threading.ThreadHelper.ThreadStart()
24: InnerException:
Here line 6 references to line 41 which corresponds to line 9 in the second example and we lost the real origin of the exception: line 4! That’s what you should keep in mind: throw the original exception and not that one you caught before.
2.2.2 When to catch exceptions
Exceptions should be catched when you know how to handle them or you need to trace them. By consequence you don’t treat all exceptions that a method might throw. Bubbling up (letting this exception go up) these exceptions until they can be treated or even to it’s last level (the unhandled exception) is the cleanest way you can handle exceptions. If you weren't able to handle an exception in your application, it will and should (!) result in an unhandled exception:
.Net gives you the possibility of handling unhandled application quite smoothly as described on Msdn, or by Gaurav Khanna's article CLR Inside Out but in my opinion this should only be used to inform the user that something has gone wrong and prevent worse (for example letting the user save data before closing the application). Why? Because an exception has thrown and so somewhere in your code something did not do what it was intended to do and by consequence there is a huge risk that your application is now in an inconsistent state. What to do instead? Reproducing and debugging! Find the cause and change your code. Anyway using Unit-Tests prevent nearly all of those troubles and having a new unhandled exceptions means that you should create a new test.
2.2.3 Be precise with your exceptions
In contrast to X++, you can derive from System.Exception and create your own exceptions which gives you a much preciser technique to handle your errors. In some documentations you might find the opinion, that custom exceptions should derive from System.ApplicationException. I don’t agree, since there are many .Net Framework-related exceptions derived from System.ApplicationException and so this System.ApplicationException class does not make any sense for me. We have the following source-code:
1: try
2: {
3: throw new ApplicationException("a message");
4: }
5: catch (ApplicationException aex)
6: {
7: System.Diagnostics.Trace.Write(aex);
8: throw;
9: }
10: catch (MyApplicationException aex)
11: {
12: System.Diagnostics.Trace.Write(aex);
13: throw;
14: }
15: throw new ApplicationException("some tesxt");
Unfortunately the line 10 with the exception MyApplicationException (derived from ApplicationException) will never be caught, even if this exception has been thrown. Be aware of the object hierarchy and sort your exception from most precise to general. Be aware of the object hierarchy. If you would have reversed the MyApplicationException with the ApplicationException, you would have been able to have more precision about your exception.
2.2.4 Wrapping the original exception
Sometimes the original exception is only a symptom of something completely different. In that case you might prefer to throw your own (or at least the real) exception, but without loosing precision. In that case, throw the new exception that wraps the original one:
1: try
2: {
3: //your code
4: }
5: catch (AnyException aex)
6: {
7: System.Diagnostics.Trace.Write(aex);
8: throw new ApplicationException("this happens with a wrong config", aex);
9: }
The point here is that you give the original exception (aex) to the new created exception and so this information isn't lost. Let’s take a second example. This time it is close to the two examples that were already discussed. The code is the following:
1: try
2: {
3: throw new ApplicationException("a message");
4: }
5: catch (ApplicationException aex)
6: {
7: System.Diagnostics.Trace.Write(aex);
8: throw new ApplicationException("another text", aex);
9: }
The line 9 now throws the following exception:
1: System.ApplicationException was unhandled
2: Message="another text"
3: Source="WinForm"
4: StackTrace:
5: at WinForm.Form1.button2_Click(Object sender, EventArgs e) in C:/.../Form1.cs:line 57 (line 57 corresponds to line 8 in the sample)
6: at System.Windows.Forms.Control.OnClick(EventArgs e)
7: at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
8: at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
9: at System.Windows.Forms.Control.WndProc(Message& m)
10: at System.Windows.Forms.ButtonBase.WndProc(Message& m)
11: at System.Windows.Forms.Button.WndProc(Message& m)
12: at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
13: at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
14: at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
15: at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
16: at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
17: at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
18: at WinForm.Program.Main() in C:/Users/floditt/Documents/Visual Studio 2008/Projects/WinForm/WinForm/Program.cs:line 18
19: at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
20: at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
21: at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
22: at System.Threading.ThreadHelper.ThreadStart()
23: InnerException: System.ApplicationException
24: Message="a message"
25: Source="WinForm"
26: StackTrace:
27: at WinForm.Form1.button2_Click(Object sender, EventArgs e) in C:/.../Form1.cs:line 52 (line 52 corresponds to line 3 in the sample)
28: InnerException:
Line 5 refers as expected to line 8 in the sample but now we have an inner exception that refers to the line 3 and this is exactly the line we expected to have.
So to resume this: Don’t catch if you do not need to throw the original exception until you can’t add any real new information to the exception. And if you throw your own exception (based on another exception) wrap this to your new one, so that no information is lost.
2.2.4 Use Logging tools
This topic would be far beyond this subject, but please use logging frameworks like NLog or Log4Net or similar ones who will give you a really great added value. There are a lot of guides showing how to use them, so please google a little bit and find the guide that seems to be the right for you. Since I’m a big fan of NLog I would suggest having a quick look at this project, but you must know, that in most projects its Log4Net that is used. Probably because it was one of the first of its kind in Java…
If you like to use default .Net Framework code, you can still use System.Diagnostics.EventLog and it’s doing a pretty good job. But whatever you finally choose: Just be sure that you are able to monitor your application! Or maybe Microsoft's enterprise framework (EntLib) might what you are looking for?
-
2.2.5 The performance discussion
There has been a lot of discussion about performance penalties because of the use of exceptions. This might be true, and some blogs proofed that with the perfmon. But to make it clear: What are exceptions ? Keep in mind: If your application suffers of poor performance because of the use of exceptions, it’s not because of the exception management that you’re in trouble… ;-)
2.2.6 The "goto"-statement
-
Please don’t use the goto-statement to reproduce your retry from X++. The following code might do something similar as the "retry" from X++, but please think of your colleagues that will work on your code… ;
-
1: int i = 0;
2: retry_scenario:
3: try
4: {
5: i += 1;
6: if (i > 10) throw new Exception("No luck");
7: throw new ApplicationException("a message");
8:
9: }
10: catch (ApplicationException aex)
11: {
12: goto retry_scenario;
13: }
2.2.7 Don't catch everything
As I already mentioned in the X++ part (2.1.5) it is a no do to catch every exception without knowing how to handle them. A typically sign of desperation is the following code:
1: //your code so far
2: catch
3: {
4: //do something
5: }
There was a time before the CLR2 where it was sometimes necessary to catch exceptions that were not derived from System.Exceptions (the non CLS-compliant ones) and this was the only way to catch them. Nowadays there is no excuse for such a code anymore.