Extensible X++ – Method signatures


 

Method signatures are not extensible – and will not be.

 

Extensible parameters would be intrusive – in so many ways:

  • Breaks derived classes (signatures of overridden methods must match),
  • Requires recompilation,
  • No side-by-side support.

 

Here are some alternatives.

 

The request for adding a new parameter to a method as an extension is surfacing quite frequently. A method's signature is the contract between caller and method.  When new parameters are added, then callers can provide additional data that the method can act on. In isolation; a request for an extra parameter makes no sense. An extra parameter will not help anyone unless someone is passing it in, and someone is acting on it.

In the land of extensibility there are restrictions:

  • You cannot change the places a given method is called, that would require overlayering.  But you can call the method from your own code (with control over the parameters).
  • You cannot change the implementation of the method, that would require overlayering. But often you can add logic pre/post or wrap using Chain-of-command.

With these constraints, here are some options:

Option 1: "Method overloading"

Scenario

You need to pass extra information from your code to an existing method and act on it in a pre/post handler.

Description

X++ doesn't support overloading methods – but you can mimic the behavior by creating a new method (with new name) in an extension class. The extension method can take additional parameters and call the original method; with your logic before and after calling the original method.

Example

Here is an example of "overloading" the insert method on CustTable. Notice the first 3 parameters are identical to the parameters on the original insert() method.


Option 2: Class state

Scenario

You need to pass extra information from your code to a method on a class that is called via other method(s) on the class.

Description

X++ now supports adding class state via an extension. You can use this to store the extra information.

Example

Here is an example adding state and wrapping a method.

Here is an example using this. The standard implementation of initFromItemOrCategory() calls initFromInventTable().

 

Option 3: Disposable context

Scenario

You need to pass extra information from your code to a pre/post handler somewhere downstream.

Description

It can be tempting to store the extra information in a global cache or variable. There is a better approach, which avoids stale data, and is type-safe. Create a singleton class for the context. The class must implement System.IDisposable, so it is disposed when it goes out of scope. The receiving code can access the singleton instance to extract the information.

Example

Here is an example of the context class:

Here is an example of the calling code:

Here is an example of the consuming code. The standard implementation of CustTable.Insert() calls DirPartyTable::createNew() – the example uses Chain-of-command to wrap the createNew method, and then accesses the context to get the information.

Caution

Transferring state from one arbitrary place to another using a global variable (which the context class is) can lead to future logical errors – that can be very hard to detect and fix. My recommendation would be to limit the usage to situations where the scope and consequences are manageable.

Option 4: Request new extension points

Now, the above mechanisms cannot solve the cases where existing callers need to provide more data, or where the existing method implementation needs to act on the data. That would require overlayering. If you discover a need for this or another case where you are not comfortable with the above options, please log an extension request explaining your scenario. If you've read so far, it should now be obvious that you need more than changing a method's signature – you need an extension point, the implementation of this might require changing a method's signature – but that is just a technical detail.

 

P.S. If you have other suggestions for how to solve situations like these, please leave a comment.

 

THIS POST IS PROVIDED AS-IS AND CONFERS NO RIGHTS.


Comments (2)

  1. mcounter says:

    Option 3 looks very risky, and will work reliable only in case different user sessions work in separate .NET domains and classes not share static variables across domains. The same must be true for both main AOS instances – IIS-hosted and Batch service. Only MS has information how it’s implemented on core level. If MS can confirm that each user-session and batch thread is domain-independent and not share static objects, this is really useful approach. Otherwise it leads to hardly-discoverable errors caused by using same variable across session.

    I suggest Microsoft design reliable framework which allow discover and impact on standard code states from external trusted extensions. Very often we use additional parameters to change standard method behavior only in case it’s called from particular standard place. Today we cannot do it without overlayering. But if only we have access to call-stack and it variables in the same way we have it from Visual Studio debugger, it will be power way to avoid overlayering, parameters extension and code duplicates.

    Example: Method B.m1() is called from classes A1 and A2, … An and return some calculated amount. But we want change it behavior only if it’s called from class A2.m2(). In AX2012 we could add one-more parameters or call separate method. But in AX7 we want avoid overlayering. We use chain of command and wrap B1.m1() method. After next call we want change returned value, but only in case it was called from A2.m2() and probably use some variables from A2.m2(). We have no any trusted way to do that, but it’s technically possible, because this information is accessible from debugger and can be accessible from X++ code.

    Sure, it sounds like dangerous approach, but really not better than overlayering.

    1. Hi mcounter,
      Thanks for the comment. There is no sharing of static members with other threads/batches/AOSes/etc. This was also mentioned in this blog post in one of the comments: https://blogs.msdn.microsoft.com/mfp/2015/12/08/x-in-ax7-static-members/
      I’m not getting your example – what will that enable that option 3 above doesn’t? I discourage dependencies on call stacks – those are subject to change, and will break your code when upgrading. The disposable context is more reliable.
      Happy extending,
      Michael

Skip to main content