name: xpp-class-and-method-rules description: Rules for X++ class declarations, method modifiers, parameter passing, constructors, and extension methods (separate from CoC). Invoke when authoring new classes, overrides, factory methods, or extension methods. applies_when: User intent involves declaring an X++ class, choosing access modifiers, designing a constructor, overriding a method, or creating extension methods.
⛔ NEVER write X++ AOT XML files directly via PowerShell, terminal file commands (
Set-Content,Out-File,New-Item), editor write tools, or any raw text approach. The XML schema (<AxClass>,<AxTable>,<AxForm>,<Methods>,<SourceCode>) is proprietary — LLMs have not been trained on it reliably. ALWAYS used365fo generate …commands to produce correct AOT XML. Ifd365fois unavailable in PATH, stop and ask the user to install it.
X++ class & method authoring rules
Source of truth: learn:xpp-classes-methods. Pre-flight:
d365fo get class <Name> --output jsonfor the base class signatures andd365fo find usagesbefore any refactor.
Class-level rules
- Default class access =
public. Removingpublicdoes not make a class non-public. Use:internalto scope to the same model.finalto prevent extension by inheritance (enables CoC instead).abstractfor base-only types (cannot mix withfinal/static).
- Instance fields default =
protected. NEVER make instance fieldspublic. Expose state viaparmFooaccessors. Public fields tightly couple consumers to internal layout and break encapsulation. - Constructor pattern: one
new()per class (compiler generates an empty default if absent). Convention:protected void new()— internal use only.public static MyClass construct()— factory entry point.protected void init(...)— post-construction setup.
Method modifier order
[edit | display] [public | protected | private | internal] [static | abstract | final]
static finalis permitted;abstractcannot mix withfinal/static.- Override visibility rule: an override must be at least as accessible as the base method.
public→publiconly;protected→publicorprotected;private→ not overridable.
Parameters
- Optional parameters must come after all required parameters. Callers cannot skip — every preceding parameter must be supplied.
- Use
prmIsDefault(_x)inside aparmX(_x = x)accessor to detect "was this caller-supplied?". - All parameters are pass-by-value. Mutating a parameter inside the method does NOT affect the caller's variable. Return modified state explicitly or wrap in an object.
this rules
- Required (or qualified) for instance method calls.
- Cannot qualify class-declaration member variables — write the bare name.
- Cannot be used in a
staticmethod. - Cannot qualify static methods — use
ClassName::method().
Extension methods (NOT CoC — these are adders)
Targets: Class / Table / View / Map.
- Extension class must be
static(notfinal); name ends with_Extension. - Every extension method is
public static. - First parameter is the target type — the runtime supplies the receiver; the caller does not pass it.
public static class CustTable_Extension
{
public static AmountMST balanceWithBuffer(CustTable _custTable, AmountMST _buffer)
{
return _custTable.balanceMST() + _buffer;
}
}
// Caller — first param is omitted:
amount = custTable.balanceWithBuffer(1000);
Constants & locals
- Constants over macros.
public const str FOO = 'bar';at class scope (cross-referenced, scoped, IntelliSense-aware) instead of#define.FOO('bar'). Reference viaClassName::FOO. varkeyword for type-inferred locals when the type is obvious from the right-hand side (var sum = decimal + amount;). Skipvarwhen the RHS is non-obvious — readability beats brevity.- Declare-anywhere encouraged — declare close to first use, smallest scope. The compiler rejects shadowing of outer-scope variables with the same name.
Hard "never" list
- Never make instance fields
public. - Never call
[SysObsolete]methods — read the attribute message for the replacement. - Never skip
/// <summary>doc comments on public/protected members (BPBPXmlDocNoDocumentationComments). - Never override a method without
d365fo get class <Base>to confirm the exact signature.
Pre-flight commands
d365fo get class <Class> --output json # methods, attributes, signatures
d365fo read class <Class> --method <m> --declaration # exact return type & params
d365fo find usages <method> --output json # caller risk