Macros in X++
Learn what macros are in X++, when they're useful, and why they should be used carefully.
Macros in X++#
Macros in X++ let you define reusable pieces of code or values that can be referenced throughout your application.
If you have worked with older X++ code for any amount of time, you have probably already seen them. Macros were used heavily in AX and you will still find plenty of them throughout the standard D365FO application.
They can be useful, especially when working with existing code, but they should be used carefully. In most new development, there is usually a cleaner option such as a constant, enum, static method, or regular class.
What is a macro in X++?#
A macro is basically a named piece of text that the X++ compiler substitutes into your code before compiling it.
The simplest example looks like this:
#define.DefaultBatchSize(100)
public void processRecords()
{
int batchSize = #DefaultBatchSize;
info(strFmt("Batch size: %1", batchSize));
}In this example, we define a macro called DefaultBatchSize with a value of 100.
Anywhere we use:
#DefaultBatchSizethe compiler substitutes the macro value.
Conceptually, the code becomes:
int batchSize = 100;That is the basic idea behind X++ macros.
When would you use this?#
Macros are most useful when you need to work with existing D365FO code that already uses them.
For example, you may come across code like this:
#define.CurrentVersion(1)or:
#define.Version1(1)
#define.Version2(2)Macros are also commonly used with pack() and unpack() methods for versioning class state.
You may also see macros used to define:
- Constant values
- Version numbers
- Reusable expressions
- Lists of fields
- Query-related values
- Legacy framework behavior
For new development, though, I would not automatically create a macro just because you need to reuse a value.
If a constant, enum, or method can do the same job and make the code easier to understand, I would normally use that instead.
Defining a macro#
A macro is defined using #define.
#define.MaxRetries(3)You can then reference it using:
#MaxRetriesFor example:
public void process()
{
int retryCount;
while (retryCount < #MaxRetries)
{
retryCount++;
// Processing logic
}
}Instead of hardcoding 3 throughout the code, the value is defined once.
That makes it easier to understand what the value represents and gives you one place to change it.
A practical example#
One place where you will commonly see macros in X++ is class versioning.
For example:
#define.CurrentVersion(1)
public container pack()
{
return [#CurrentVersion];
}The CurrentVersion macro identifies the current version of the packed data.
You may then see something like this in unpack():
#define.CurrentVersion(1)
public boolean unpack(container _packedClass)
{
int version = RunBase::getVersion(_packedClass);
switch (version)
{
case #CurrentVersion:
return true;
}
return false;
}This is a good example of why understanding macros is still important.
Even if you do not create many macros yourself, you will absolutely run into them when reading existing D365FO code.
Macro libraries#
Macros can also be stored in a macro library and included in other code.
You may see code like:
#MyMacroLibraryThis makes the macros defined in that library available to the code that includes it.
For example, a macro library might contain:
#define.DefaultTimeout(30)
#define.MaxRetries(3)A class could then include the library:
#MyMacroLibrary
public void process()
{
int timeout = #DefaultTimeout;
int retries = #MaxRetries;
}This was a common way of sharing constants and reusable definitions across AX code.
Again, you will see this more often when working with existing Microsoft or legacy custom code than when writing brand-new D365FO functionality.
Macros vs constants#
If all you need is a reusable value, a constant is usually easier to understand in modern X++ development.
Instead of:
#define.MaxRetries(3)you could use:
private const int MaxRetries = 3;Then reference it normally:
public void process()
{
int retryCount;
while (retryCount < MaxRetries)
{
retryCount++;
// Processing logic
}
}The constant is easier to discover, easier to navigate in Visual Studio, and behaves like a normal X++ member instead of compiler substitution.
That is generally the direction I would go for new code.
Macros vs enums#
Another common mistake is using macros to represent a fixed list of related values.
For example:
#define.StatusOpen(1)
#define.StatusClosed(2)
#define.StatusCancelled(3)This works, but an enum is usually a much better fit.
An enum gives the values actual meaning in the type system and makes the code much easier to read.
Instead of:
if (status == #StatusClosed)
{
// Do something
}you can have something closer to:
if (status == MyStatus::Closed)
{
// Do something
}The second version is much easier to understand when you come back to the code six months later.
Best practices#
When working with macros in X++, I would keep a few things in mind:
- Use macros when the existing framework or pattern expects them.
- Understand macros because they are still common in standard D365FO code.
- Avoid creating new macros when a constant, enum, or method is a better fit.
- Give macros descriptive names.
- Avoid hiding complicated logic inside macros.
- Keep macro libraries focused instead of turning them into a dumping ground for unrelated values.
- Do not use macros just to save a couple lines of code.
The goal should always be readable code.
Saving three lines with a clever macro is not worth making the next developer spend 20 minutes figuring out what it does.
Common mistakes#
Using macros for everything#
Just because macros can reduce duplicate code does not mean they should be the default solution.
Before creating one, ask whether you could use:
- A constant
- An enum
- A method
- A class
- An EDT
Most of the time, one of those will make the intent clearer.
Hiding too much logic#
Macros are text substitutions performed during compilation.
If a macro contains a large amount of logic, debugging and understanding the code becomes harder.
Keep them simple.
Assuming a macro is a variable#
A macro is not a normal X++ variable.
For example:
#define.MaxRetries(3)does not create an int variable called MaxRetries.
The compiler replaces:
#MaxRetrieswith the value defined by the macro.
That distinction is important when you are trying to understand existing code.
Replacing existing framework macros unnecessarily#
You will find macros throughout standard D365FO code.
If Microsoft is using a macro as part of an established framework or pattern, there usually is not a good reason to rewrite everything just because you would not use a macro in new code.
Understand the pattern first and follow it where appropriate.
Related articles#
- [X++ Classes Explained]
- [X++ Methods Explained]
- [X++ Constructors]
- [Static vs Instance Methods in X++]
- [Enums in X++]
- [Extended Data Types (EDTs) in D365FO]
Conclusion#
Macros are one of those X++ features that you should understand even if you do not use them very often in new development.
You will see them throughout standard D365FO and older AX code, especially for constants, versioning, shared definitions, and framework-specific patterns.
For new code, I would usually start with a constant, enum, or method and only use a macro when there is a good reason for it.
The important part is knowing what #define, #MacroName, and macro libraries are doing when you run into them.
Once you understand that a macro is basically compiler-level text substitution, the code becomes a lot less confusing.
Missing something?
Suggest a topic you'd like to see covered next. The most requested topics help shape future XppForge documentation.
Sign in to suggest a topic