Wednesday, June 13, 2018

Accidental Code Extensions

Ok, I'll preface this by saying I'm very much aware that the standard X++ code in platform and application has this issue too. Thanks for letting me know :-) But as the saying goes: do as I say - not as I do...

With that out of the way... Going back in time, the 7.0 X++ language supported extension methods like C#. You create a new class with any name, but ending in _Extension. Then, you can add a new public static method, and the first parameter is the object you're extending.
So for example:

static class MyExtensions_Extension
{
    public static void foo(PurchTable _purchTable)
    {
    }

    public static void bar(SalesLineType _salesLineType)
    {
    }
}
This class adds extension method foo() to the PurchTable table and method bar() to the SalesLineType class. This feature is now less used due to the [ExtensionOf()] "augmentation" class paradigm where you can have instance method, add member variables, access protected members, etc. However, the original extension method feature still exists, and in fact many people accidentally use it!

The issue happens when adding both extension methods and event handlers in the same class. In theory this sounds great - you have all your extensions in one place. For example:

static class MyPurchTableExtensions_Extension
{
    public void foo(PurchTable _purchTable)
    {
    }

    [DataEventHandler(tableStr(PurchTable), DataEventType::Inserting)]
    public static void HandlePurchInserting(Common sender, DataEventArgs e)
    {
    }
}

This works as expected - a new method foo() is added to PurchTable, and you're handling the inserting event. However, the unintended consequence is that you are ALSO adding a new method HandlerPurchInserting(DataEventArgs e) on the Common object! The compiler does not discriminate the fact that you have a handler attribute on that method. All it sees is you're adding a static method in an _extension class, with one or more arguments.


So... How many methods have you accidentally added to Common, XppPrePostArgs, FormRun or FormControl? :-)

4 comments:

  1. 1. When using [ExtensionOf()] class is not static.
    2. 4th rule of old AX7 documentation extension methods is not fulfilled. Rule states: "The first parameter in every extension method is the type that the extension method extends. However, when the extension method is called, the caller must not pass in anything for the first parameter. Instead, the system automatically passes in the required object for the first parameter."

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Good post Joris,
    I believe adding the link below will be much easier with the post.
    https://docs.microsoft.com/en-us/dynamics365/unified-operations/dev-itpro/extensibility/add-method-table

    ReplyDelete
  4. Joris, thank you for this info. I'm still a little unclear of exactly which scenarios do or do not trigger this unintended consequence of extension methods on common objects when event handlers are intended.

    Does the use of the ExtensionOf attribute eliminate this issue, or will you still have the problem for any X++ class if the class is static and ends with "_Extension" regardless of if you use the ExtensionOf attribute or not?

    For example, would these have the same issue?
    Scenario 1: (not sure why this class is static instead of instance, but took this from an example in our current implementation)
    [ExtensionOf(tableStr(PurchLine))]
    public static class PurchLine_xxxx_Extension
    {
    [PreHandlerFor(tableStr(PurchLine), tableMethodStr(PurchLine, modifiedField))]
    public static void PurchLine_Pre_modifiedField(XppPrePostArgs args)
    {
    //will this create extension method on XppPrePostArgs type?
    }
    }

    Scenario 2:
    [ExtensionOf(classStr(Global))]
    final static class Global_xxxx_Extension
    {
    public static str xxCustomStrReplace(str _str, str _fromStr, str _toStr)
    {
    //The Global class is static, so I think the ExtensionOf need to be also, but does this add an extension method to the type str?
    }
    }

    ReplyDelete