Class extensions

The new Dynamics AX (AX 7) attempts to minimize overlayering (“customization”) and promote extensions, which allow adding functionality without modifying source code of the original object. For example, you now can attach a field to a table without changing the definition of the table itself. It has many benefits – no changes in existing objects mean no code conflicts and much easier upgrades, it’s not necessary to recompile the original object and so on.

AX 7 RTW introduced extension methods – if you’re not familiar with them, look at my description and an example in New X++ features in AX 7. In short, extension methods are static method defined in a completely different class, yet they can be called in the same way at if they were instance methods of the “extended” class, table or so.

Update 1 added something called class extensions, which allows adding not only methods, but also class variables, both instance and static, constructors and static methods.

For example, the following piece of code augments the standard Dialog class with a variable and methods working with the variable:

[ExtensionOf(classStr(Dialog))]
final class MyDialog_Extension
{
    private int x;
 
    public void setX(int _x)
    {
        x = _x;
    }
 
    public int getX()
    {
        return x;
    }
}

You can use these methods directly on the Dialog class:

Dialog d = new Dialog();
d.setX(50);  
int x = d.getX();

As you would expect, the value set by setX() is stored in the variable and you can retrieve it by calling getX().

But how does it really work? How Dialog class knows about these new methods and where is the value actually stored, if it’s all defined in a completely different class?

You might think that MyDialog_Extension inherits from Dialog and replaces the Dialog class, it’s a partial class or something like that, but it’s not the case.

The methods are actually extension methods as we know them from AX 7 RTW. Don’t get confused by the fact that we declare them as instance methods; they’re converted to normal static extension methods under the hood. For example:

// This instance method in the extension class…
public void setX(int _x) {}
 
// …is converted to a static extension method like this:
public static void setX(Dialog _dialog, int _x) {}
// As usual, the first argument is object to which the extension methods applies.

The conversion happens when X++ is compiled to CIL and you normally don’t have to bother about it; I’m mentioning it to demonstrate that it’s really just a normal extension method.

Generating different CIL than what you see in X++ might look weird, but it’s a common way to extend CLI (“.NET”) languages without changing the Common Language Runtime (CLR). For example, if you use async keyword in C#, C# compiler uses standard CLR constructs to generate a whole state machine that handles asynchronous processing. Because CLR receives normal CIL code, it doesn’t need to know anything about X++ class extensions nor C# async/await.

All right, so methods in class extensions are just common extension methods, which explains how methods are “attached” to the augmented class. But where AX stores the variables? How can static methods of the extension class access instance variables?

Again, the solution isn’t too complicated. Under the hood, the extension class has a static variable which maintains references between instances of the augmented class and instances of the extension class. In my case, it maps instances of Dialog and MyDialog_Extension.

When I call a method of my MyDialog_Extension, it gets a Dialog instance as the first argument. It looks into the static map, obtains the corresponding MyDialog_Extension instance (it might have to create it first if it doesn’t yet exist) and then it accesses its fields.

The following code is a simplified demonstration of how it works.

// A demonstration - not the real implementation
final class MyDialog_Extension
{
    private int x;
    private static Map mapDialogToExtension;
 
    public static void setX(Dialog _dialog, int _x)
    {
        MyDialog_Extension extension = mapDialogToExtension.lookup(_dialog);
        if (!extension)
        {
            extension = new MyDialog_Extension();
            mapDialogToExtension.insert(dialog, extension);
        }
        // Here we're accessing the instance variable
        extension.x = _x;
    }
}

Now it’s clear that extension classes don’t modify their augmented classes in any way. All variables declared in an extension class are stored in its instances – it’s rather similar to joining two separate tables.

Because extension classes merely get instances of augmented classes by references, they can’t access its internal state or call private methods.

You can also attach static methods and class fields (variables and constants).

Using static methods in class extensions is useful because it’s easier to find static methods on the corresponding class rather in some utility classes. It also allows you to add methods to the Global class and call them without specifying any class name.

Using static fields can also be very useful. The following example shows adding a new operator (implemented as a public constant) to DimensionCriteriaOperators class.

[ExtensionOf(classStr(DimensionCriteriaOperators))]
final static class MyDimOperators_Extension
{        
    public const str MyNewOperator = '!';
}

As with methods, the new constant seems to be a part of the augmented class, although it’s declared somewhere else.

ExtensionPublicConst

In some cases, such a static class with public constants can be a natural replacement of extensible enums.

If you use a static method or a static variable, CIL generated by X++ compiler directly refers to the extension class.  For example, DimensionCriteriaOperators::MyNewOperator is translated to exactly the same CIL code as MyDimOperators_Extension::MyNewOperator. The fact that X++ presents it in a different way is purely for developers’ convenience.

As you see, class extensions are really an evolution of extension methods. Instance methods in class extensions are based on extension methods; they “merely” add a the option to work with instance variables. And there a few other useful features, namely constructors and static members.

The main purpose of class extensions is to help with getting rid of overlayering, but that’s not the only use. They have potential to change how we traditionally design certain things in AX, especially if somebody comes with a common framework based on extensions, as LINQ revolutionized how we work with collections in C#.

For example, are you aware of that you can write extension methods working with all table buffers by extending Common? Like this:

[ExtensionOf(tableStr(Common))]
final class Common_Extension
{
    public List getSelectedRecords()
    {
        List selectedRecs = new List(Types::Record);
 
        if (this.isFormDataSource())
        {
            MultiSelectionHelper helper = MultiSelectionHelper::construct();
            helper.parmDatasource(this.dataSource());
 
            Common rec = helper.getFirst();
            while (rec)
            {
                selectedRecs.addEnd(rec);
                rec = helper.getNext();
            }
        }
        return selectedRecs;
    }
}

The possibilities are unlimited.

I hope I didn’t overwhelm you with technical details – it may be challenging especially if you’re not familiar with C#, where we use things like static members and extension methods for years. But I believe that understanding the inner workings is often very helpful for using language features correctly and debugging code if it doesn’t work as expected.

Label ID fix

I recently saw an interesting problem with labels in AX 2012 – two customizations created different labels with the same IDs, therefore the newer won and the older one showed completely wrong texts in GUI.

For example, one customization created a form with caption @XYZ1 = “Customers”. Another customization created a label with the same ID, @XYZ1, with text “This is an error” and used the label for an error message. The latter change effective replaced the text of @XYZ1 from “Customers” to “This is an error” and therefore the form caption becomes a nonsense.

The cause (I believe) was that two developers used different primary language when putting code to TFS and not all labels existed in all languages. Anyway, the main question was how to fix it.

There is no way how @XYZ1 could mean both “Customers” and “This is an error” at the same time, therefore it was necessary to create a new label for one of the cases and update all code using the label. And repeat it for more than a hundred times.

I didn’t know how difficult it would be to automate it – fortunately I found it rather easy. First of all, I extracted original labels from an older version of the .ald (version control systems can’t prevent all problems, but at least you have all data so you can recover from failures). Then I parsed this list of labels to objects containing label ID, label text and description. (Note that I didn’t have to deal with translations in this case.) You can see it in getParsedLines() method below.

Next step was to create a new label ID for the given text and description and maintain a mapping between the old and the new label ID.

Finally, I used some existing methods to replace original labels IDs with new ones in code and properties. This was potentially the most complicated part, but it turned out to be a piece of cake. :-)

I also had to identify and checked out all objects where labels should have been be replaced, because the replacement works only with checked-out objects. It wasn’t too difficult thanks to version control; I simply took all objects included in the original changeset.

The complete code is below; maybe somebody will run into a similar problem and will find this useful.

class DevLabelFix
{
    private List getParsedLines()
    {
        str fileName = @'D:\oldLabelsFromTFS.txt';
        System.Collections.IEnumerable lines;
        System.Collections.IEnumerator enumerator;
        str line;
        DevLabelDef labelDef;
        List parsedLines = new List(Types::Class);
        int spacePos;
 
        try
        {
            lines = System.IO.File::ReadAllLines(fileName);
            enumerator = lines.GetEnumerator();
 
            while (enumerator.MoveNext())
            {
                line = enumerator.get_Current();
 
                if (strStartsWith(line, '@'))
                {
                    labelDef = new DevLabelDef();
 
                    spacePos = strFind(line, ' ', 1, 10);
                    labelDef.parmId(subStr(line, 0, spacePos-1));
                    labelDef.parmLabel(subStr(line, spacePos+1, strLen(line)));
                    parsedLines.addEnd(labelDef);
                }
                else if (line != '')
                {
                    Debug::assert(labelDef != null);
                    labelDef.parmDescription(line);
                }
            }
        }
        catch (Exception::CLRError)
        {
            throw error(AifUtil::getClrErrorMessage());
        }
 
        return parsedLines;
    }
 
    public void run()
    {
        ListEnumerator enumerator = this.getParsedLines().getEnumerator();
        DevLabelDef labelDef;
        SysLabelEdit sysLabelEdit = new SysLabelEdit();
        str newLabelId;
        Map labelMap = new Map(Types::String, Types::String);
 
        while (enumerator.moveNext())
        {
            labelDef = enumerator.current();
 
            newLabelId = sysLabelEdit.labelInsert(  'en-us',
                                                    labelDef.parmLabel(),
                                                    labelDef.parmDescription(),
                                                    SysLabelApplModule::None,
                                                    'XYZ');
            info(strFmt("%1|%2", labelDef.parmId(), newLabelId));
 
            labelMap.insert(labelDef.parmId(), newLabelId);
        }
 
        // These methods are normally private; I made them temporarily public to allow these calls.
        SysLabelFile::preCheckInUpdateAllPendingFiles(labelMap);
        SysLabelFile::preCheckInUpdateAOTElementsClient(labelMap);
    }
 
    public static void main(Args args)
    {
        new DevLabelFix().run();
    }
}
 
// The DevLabelDef class merely holds ID, label text and description together.
class DevLabelDef
{
    str id;
    str label;
    str description;
 
    public str parmDescription(str _description = description)
    {
        description = _description;
        return description;
    }
 
    public str parmId(str _id = id)
    {
        id = _id;
        return id;
    }
 
    public str parmLabel(str _label = label)
    {
        label = _label;
        return label;
    }
}

Fonts and colors in Visual Studio

Moving Dynamics AX development to Visual Studio gave us a state-of-art IDE with many features and configuration options. For instance, you can modify fonts, font sizes and color to match your preference and to help to overcome certain visual impairments.

These options are available from Options > Environment > Fonts and Colors. You can configure many things there, such changing colors of the Output Window or setting Environment Font to Snap ITC (try it :-)), nevertheless let’s look at Text Editor right now.

GeneralOptions

There are some generic options, such as the background color, and then some language-specific settings. This includes X++, therefore you can customize, for example, how overlayered code is displayed in code editor.

XppOptions

Personally I haven’t changed any of these setting for X++, but it’s good to know about them.

Custom code snippets in AX 7

I already explained how to use code snippets and a problem you might run into, but what if you want some additional snippets?

The good news is that you can easily build new snippets by yourself. Let’s use a concrete example.

Let’s say that you often write queries with the query framework and execute them with QueryRun and you’re tired of writing the boilerplate code. A snippet would help.

First of all, create a text file and name it queryRun.snippet. Then fill it with the following content:

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>queryRun</Title>
      <Description>Creates a query with a single table, executes in and fetches records.</Description>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
    </Header>
    <Snippet>
      <Declarations>
        <Literal Editable="true">
          <ID>table</ID>
          <ToolTip>Table name</ToolTip>
          <Default>MyTable</Default>
        </Literal>
        <Literal Editable="true">
          <ID>buffer</ID>
          <ToolTip>Table variable name</ToolTip>
          <Default>myTable</Default>
        </Literal>
      </Declarations>
      <Code Language="X++"><![CDATA[Query query = new Query();
QueryBuildDataSource qbds = query.addDataSource(tableNum($table$));
QueryRun queryRun;
$table$ $buffer$;
 
queryRun = new QueryRun(query);
while (queryRun.next())
{
    $buffer$ = queryRun.get(tableNum($table$));$end$
}]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

In short, the header describes the snippet and can provide additional information. Literals define places that can be modified after putting the snippet into code editor, such as the name of the table. The code structure will be the same in all cases, but you’ll likely want to use various tables, therefore the table name is a good candidate for a literal.

The last section, Code, contains the actual source code. Literals are represented by their IDs surrounded by dollar signs (by default).

You can learn more about the syntax in Code Snippets Schema Reference.

When you have your snippet ready, open Visual Studio, go to Tools > Code Snippet Manager, change Language to X++ and click Import.

Import snippet

Select the snippet file and put it into Snippets folder.

Import snippet location

Confirm dialogs and look at snippets in code editor – you should see your new queryRun there:

queryRun selection

Use the snippet and you should get the following code, where you’ll set the table type and the name of the variable.

queryRunResult

Note that the problem with replacements applies here, therefore you have to be careful where you use the snippet. It seems that the while loop inside the snippet triggers code indentation (although I haven’t investigated it in detail), therefore you should use the snippet in some invalid context, such as directly in class declaration or in a method with a compilation error. This workaround won’t be needed in future, after the bug gets fixed.

After typing in just two words (names of the table and the variable), you have a simple query ready to run and you can focus on your actual business logic:

query worker

Another note: If you follow this particular example with worker, you’ll notice that when you type worker and press Tab, code completion kicks in and replaces worker with WorkerSessionType. To get the intended result, you have to press Esc instead of Tab.

When you write code, think about what you do often and consider turning it into a snippet, so you don’t have to write it by hand again. You also don’t have to build snippets just for yourself – you can share them with your team or even the whole world.

Editor scripts in MorphX allow any arbitrary code, which clearly isn’t the goal of code snippets in Visual Studio. But it doesn’t mean that you can’t do that – if you need something more complex, such as generating a whole class based on input from a dialog, you can create a Visual Studio extension.

Code snippets in AX 7 – The Problem

In the previous blog post, I demonstrated how to use several code snippets in Visual Studio. I also mentioned that there is a problem – here I’m going to look at it more closely.

Let me introduce yet another snippet, just to make it a little bit more interesting.

propfull selection

propfull is modeled on a snippet of the same in C#, where it creates a property with a backing field (member variable). Because X++ doesn’t have properties, it creates an accessor method instead.

This is the expected behaviour, when you can replace the data type (int by default), the variable name (myVar) and the method name (MyProperty).

propfull result correct

But it doesn’t always work. In some cases, you get only static code and you would have to replace all values by hand (three occurrences of the type, three of the variable name, one method name).

propfull result wrong

This is clearly wrong and renders the snippet much less valuable.

The obvious question is why it works in some cases and it doesn’t in other situations. It took me a while to spot it, nevertheless it seems that I’ve finally found the cause.

The snippet works (you get the option to replace placeholders) if the editor doesn’t try to indent it.

Here are a several examples of what I mean.

Example 1:

I insert propfull snippet at the beginning of the line.

1a

What I get is nicely indented code, but it’s static, without the option to replace literals.

1b

Example 2:

Now I do same, but I do it with the cursor indented with four spaces.

2a

Voilà, now literals work.

2b

I believe it’s because the starting point was already indented.

Example 3:

Just to prove the point, let’s try indention with eight spaces.

3a

As expected, code is indented but static.

3b

Example 4:

The snippet also works if I use it in an invalid context.

4a

The following code doesn’t even compile, but replacements work as expected.

4b

I think that the editor doesn’t bother to indent code, because it has no idea how to deal with code that doesn’t make sense.

Example 5

Speaking of invalid contexts, let’s try to put a for loop directly inside a class declaration.

5a

Replacements work.

5b

It might look like that editor actually did some indentation, but it’s not the case. It merely copied the definition of the snippet, which in this case includes some spaces.

Example 6

If we insert the snippet at the right place including indentation, it works.

6a

6b

Example 7

As expected, using the snippet with different indentation results in indented code without replacements.

7a

7b

Example 8

I mentioned that the editor doesn’t indent invalid code. If I do exactly the same as in the previous example, but my definition of For snippet has an error, the result is different.

8a

The code doesn’t compile because of the extra hash character (#). Unlike in Example 7, not it’s not indented correctly but replacement works.

8b

I hope this sufficiently explains when replacements in snippets work and when they don’t. In short, I believe that what’s responsible is the feature indenting code in the editor. I didn’t discuss in detail when exactly code indentation kicks in – it’s a bit a more complex than demonstrated above and I don’t know exact rules, but I think this is good enough to give you an idea.

It should help Microsoft to reproduce and fix the problem and I hope it will happen soon, because this issue makes snippets confusing and less useful. Code snippets are extremely helpful and every developer should use them and become more productive. Having such an issue doesn’t help the adoption.

This explanation should also help you to use snippets even before the problem is fixed. Without understanding why it works only in some cases, using snippets can be frustrating. It should be a bit easier with this knowledge.