There are times when writing X++ code that a D365 Macro makes the code easier to read, update later, and write. Essentially, a developer will specify a value in code that the precompiler will replace with other text. Therefore, the compiled code’s output does not change. However, it gives the developer more flexibility to make a single change that affects many other parts of the code quickly. All developers should know how to leverage this functionality to both save them time, and also so they understand these macros when they see them in code.
Simple Macro Example
Before explaining more about what a D365 macro is and when to use, check out this example.
internal final class tutorialMacro
{
public static void main(Args _args)
{
#define.Hello("Hello");
#define.World("World");
Info(#Hello + ' ' + #World);
}
}
First define a macro with name Hello. Then, specify that the value of the macro is the string “Hello”.
Next, do the same thing with defining the macro World. Then, set the value of the macro to the string “World”.
You might think that this looks like setting a ‘variable’. And it is similar. However, there is an important difference. Variables can have their values changed by the system when it is running. Whereas, with a macro, the values cannot be changed, and are actually used even before the compiler compiles the code.
Therefore, the code above is turned into this text by the precompiler, then compiled.
internal final class tutorialMacro
{
public static void main(Args _args)
{
Info(“Hello” + ‘ ‘ + “World”);
}
}
Notice, how the precompiler essentially does a ‘find and replace’ of all the #Hello places in code and replaces them with “Hello”. And it does the same thing with #World, replacing them with “World”.
The final output looks like this:

Create a D365 Macro
Interestingly, a D365 Macro can be defined in a few different places.
First, a macro can be defined in code, such as in a method, or in a class. In that case, the precompiler will look for all uses of that macro is the scope of where it is defined, and do the replacements.
Second, a macro can be defined in an Application Explorer node. Specifically, in Visual Studio go to AOT>Code>Macros.

To create a macro, in Visual Studio, right click on your project, select ‘Add New Item‘, then select ‘Macro‘ in the list of available Dynamics 365 Items.

Next, give your macro a name. I named mine ‘tutorialMacroExample’.
Visual Studio will open a blank editor window. Enter in as many macro definitions as you want.

Next, to use your macro, type #<the name of your macro node> into the code where you plan to use it. That is essentially like copying all of the macro definitions in that node into your code.
Finally, use any of the macro definitions the same way you would if you can declared the macro directly in your code. See this example of using a Macro node in code.
internal final class tutorialMacro
{
public static void main(Args _args)
{
#tutorialMacroExample
Info(#MyMacro);
}
}
Value Used In Multiple Places
While the example only replaces once instance of the macro, the real benefit comes when the macro is used in many places in code.
For example, maybe you are a developer working on a file import process. But you are not quite sure yet if the you are importing will use a comma (,) or a pipe (|) or a some other character. You can define a D365 macro is one place, use that macro throughout your code, and then update the macro definition once you know.
Note, this would save you some time finding all the places, and updating them. But even this is not the ‘best’ example, just because there are other ways you can do this.
First, you could just use a variable that you never change, like a ‘const‘. Second, you could create a method that returns the character and call that method in all of your places.
That said, whether you use a macro or something else, having a single place in code where you define important values helps developers in case they need to be changed later.
For example, in the Application Explorer under AOT>Code>Macros>File, see these common values:

If the file extension for these types of files ever changed, updating this file could quickly correct code in potentially many places spread across the entire code base.
Preferred Over Hard-Coding
Note, it is best practice to use a constant or a macro instead of hard-coding a value in code, even if you plan to only use the value in one place. A macro or constant lets the developer reading your code know that this is a value that might be used in more than one place. But it also lets them know that you specifically designed it to have that value.
Additionally, the ‘value’ might be something that is hard to understand. Therefore, using a macro name may help make the code easier to read that using the value. For example, the macro name FilePathDelimiter helps explain what the string ‘\\’ represents.
If the value is something that a user should control, then a parameter should be created, and the value stored in a table, and added to a form.
Code Used In Multiple Places
A D365 macro is best used when the value of the macro is part of the ‘syntax’ of the code, and not just a value used by the code. Look at this example:
internal final class tutorialMacroComplex
{
public static void main(Args _args)
{
#localmacro.InventDimDimensionsWhereClause
(%1.ConfigId == %2.ConfigId || ! %3.ConfigIdFlag) &&
(%1.InventSizeId == %2.InventSizeId || ! %3.InventSizeIdFlag) &&
(%1.InventColorId == %2.InventColorId || ! %3.InventColorIdFlag) &&
(%1.InventVersionId == %2.InventVersionId || ! %3.InventVersionIdFlag) &&
(%1.InventStyleId == %2.InventStyleId || ! %3.InventStyleIdFlag) &&
(%1.InventSiteId == %2.InventSiteId || ! %3.InventSiteIdFlag) &&
(%1.InventLocationId == %2.InventLocationId || ! %3.InventLocationIdFlag) &&
(%1.InventBatchId == %2.InventBatchId || ! %3.InventBatchIdFlag) &&
(%1.WMSLocationId == %2.WMSLocationId || ! %3.WMSLocationIdFlag) &&
(%1.LicensePlateId == %2.LicensePlateId || ! %3.LicensePlateFlag) &&
(%1.InventStatusId == %2.InventStatusId || ! %3.InventStatusFlag) &&
(%1.InventSerialId == %2.InventSerialId || ! %3.InventSerialIdFlag)
#endmacro
ItemId itemId = "0001";
InventDim inventDim;
inventDim.InventSiteId = "EAST";
inventDim.InventLocationId = "ANNAPOL";
InventDimParm inventDimParm;
inventDimParm.InventSiteIdFlag = NoYes::Yes;
inventDimParm.InventLocationIdFlag = NoYes::Yes;
InventSum inventSum;
InventDim inventDimFind;
select inventSum
where inventSum.ItemId == itemId
join inventDimFind
where inventDimFind.inventDimId == inventSum.InventDimId
&& #InventDimDimensionsWhereClause(inventDim, inventSum, inventDimParm);
if (inventSum)
{
Info(strFmt("The available physical inventory in Site %1, Warehouse %2 for itemId %3 is %4",
inventDim.InventSiteId
,inventDim.InventLocationId
,itemId
,inventSum.AvailPhysical));
}
}
}
Instead of just defining a single value, the macro defines several lines of code. Use the #localmacro and the #endmacro syntax.
Additionally, notice the macro value uses %1, %2, and %3 as placeholders, similar to how strfmt works, allowing dynamic values to be passed into the macro.

The code finds the inventory value of an item, in a specific site and warehouse. The table InventSum stores all of the inventory values, keeping track of how much inventory is in each inventory dimension such as ‘Site, Warehouse, Location, Size, Color, Config’.
In this example, the code find the inventory for a particular site and warehouse only. But other code might need the inventory for a different set of inventory dimensions. So a complex ‘where clause’ is needed to specify which inventory dimensions should be used.
All that is fine so far. You don’t need macro for that. However, this type of search is done in many different places throughout the system. What happens if we ever need to add an additional inventory dimension to the system?
Without a macro, a developer would have to update many places throughout the code base. However, using a D365 macro, the developer can make one change to the macro definition to add another inventory dimension, and all of the places the macro is used in code will effectively be updated.
This ability makes macros very useful in specific scenarios.
Disclaimer
The concept behind a D365 Macro is still important whether you use an x++ Macro, a constant, or something else. The idea is that you make a change in one place, and it allows your code to work differently in multiple places.
Ultimately, this saves the developer time in updating their code. If they change their mind later, they only have one place to change. Additionally, they do not need to know all of the places the macro is used to make the change. The precompiler will handle updating them.
All that said, Microsoft’s documentation says that Macros are a legacy feature and may be deprecated in future released. Currently, there is no planned release date stated.
Therefore, to be safe, it is likely best to write new code using Constants or Sysda for queries.
In the meantime, there are still many places in code that use macros. Therefore it is still important to understand how to read code with macros, and how they are used.
Conclusion
Whether D365 Macros are used by Microsoft in their code or not, it is still important to understand and read macros. Additionally, the concept of designing code in a way that changes can be made efficiently is very important. Follow these best practices to make the code more readable and easier and safer to change in the future.
Leave a Reply