Part 1: Get Set Up
Part 2: Interop Bliss
Part 3: Unfortunate Limitations
After all the glory in the last article, I'll have to end by showing you one unfortunate feature that does not work. In the previous article, we have created some managed code to which we were able to pass AX objects. Those objects translated perfectly into the proxies we were using in managed code. All this worked seamlessly. We also used a managed handler, accepting the XppPrePostArgs class, also a proxy. The event fired and the XppPrePostArgs instance was passed into our managed handler perfectly.
As you have probably read or at least heard mentioned before, there are pre/post events, and there are delegates (aka "coded events"). The pre/post can be handled in two ways. First, as in the previous example, by creating a handler that accepts the XppPrePostArgs class. The second way to handle pre/post events is by using a "lightweight handler". The lightweight event handler just needs to match the argument signature of the method it subscribes to, and return void. These handlers are called "lightweight", because they effectively give you no access to the event sender object, no access to the return value, and depending on the type of arguments (certain arguments are by default passed in as references) no means of changing the arguments to the method in case of a pre-handler. The advantage however, is that you don't have to access the arguments by means of index or name of variable, but rather get compile-time checking of your matching method arguments.
The other type of event, called a delegate, is also named a coded event because it is the original coder's intent to tell the outside world that something has happened, and allow it to react to that event. A regular convention in the .NET world, which is also recommended by the AX compiler team, is to have two arguments to the delegate: the sender object and an arguments class. The reason you want to pass an arguments class is simple. It allows the original developer of the delegate to extend the arguments being presented to event subscribers, without changing the signature of the delegate. This way existing subscribers don't break, and newer subscribers can use the extensions on the arguments class. For this purpose, AX now has a class named "XppEventArgs", which you are encouraged to inherit from to create your own event arguments.
So back to the code. Recall that in Part 1 of this series, you created a delegate on an X++ class that looked like this:
Also recall, that in part 2, you created the following managed event handler in Visual Studio:
coincidentally, they have the same signature. So, as per the definition of a delegate handler, you expect you can hook up this handler to the delegate. Well, unfortunately this is NOT supported. If we try to set this up (right-click the X++ delegate, select "New Event Handler Subscription") as a managed handler, the method dropdown is empty (it doesn't find a matching handler in our HandlerClass):
If we try to actually type in the method name of our handler manually ("ComplexHandler"... remember, ANYTHING related to managed code is case sensitive, class names, namespaces, method names, etc), you will get a compile error:
The error message actually reads "The method ComplexHandler in class -snip- has parameters of X++ complex types. It cannot be used as an event handler". I find this an interesting message, since it seems to imply that it's the handler's fault for having the wrong types, whereas it's really the X++ delegate's complex type arguments that make it incompatible with any managed handler. When I asked Microsoft about this, the simple answer was "it's not supported". Although I love the managed code interop, and I love the eventing story, apparently the two cannot be fully reconciled…
But WAIT! Did we not see in the previous article that we were able to call this ComplexHandler method from X++ code? Well yes, we did see that. And don't delegates work with X++ code in any shape or form? Well yes, they do.
So what if… we create an X++ handler, that calls the managed code method ComplexHandler and just "forwards" the arguments? Let's add a new static method to our DAXMusings class which we can use for this, let's call it "proxyHandler", and let's call the managed code within it… should work?
If you try to save this… you will get a compile error "Syntax error". Ok, I did this on purpose of course. This is a minor "glitch" I would say. The semi-colon you no longer need in AX 2012 code? Well, you still need it if your first statement is managed code (basically all of the "rules" of when it was needed in AX 2009 X++, still apply for the managed code statements in AX 2012). So, let's add the semi-colon and save the method, which will compile at this time.
Next, add the handler to the delegate. You can simply drag&drop the "proxyHandler" method onto your delegate in the AOT. All the properties will be setup correctly that way. DO NOT FORGET to rename the handler. If you want to know why this is important, check out previous articles here and here.
Ok, so this saves and compiles! What if we execute it?
Yup, our managed handler now also gets called from the delegate! So, this is quite interesting. Hooking up the managed handler directly does not work, but using this X++ "proxy" handler, yup, that works… As I stated at the beginning of this series, the AX compiler team are real smart people, so I'm sure there are good reasons for this limitation. However, we have to admit that we are missing this one feature to really call .NET a first class citizen in AX 2012. As for us, are we going to be using managed code in our implementation solutions? Absolutely.
I'm still absolutely thrilled about the managed code interop! Perhaps we can't call .NET a first class citizen, but we can sure call it a first class green card holder.