xpp-class-and-method-rules

star 12

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.

dynamics365ninja By dynamics365ninja schedule Updated 5/11/2026

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 use d365fo generate … commands to produce correct AOT XML. If d365fo is 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 json for the base class signatures and d365fo find usages before any refactor.

Class-level rules

  • Default class access = public. Removing public does not make a class non-public. Use:
    • internal to scope to the same model.
    • final to prevent extension by inheritance (enables CoC instead).
    • abstract for base-only types (cannot mix with final / static).
  • Instance fields default = protected. NEVER make instance fields public. Expose state via parmFoo accessors. 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 final is permitted; abstract cannot mix with final / static.
  • Override visibility rule: an override must be at least as accessible as the base method. publicpublic only; protectedpublic or protected; 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 a parmX(_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 static method.
  • Cannot qualify static methods — use ClassName::method().

Extension methods (NOT CoC — these are adders)

Targets: Class / Table / View / Map.

  • Extension class must be static (not final); 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 via ClassName::FOO.
  • var keyword for type-inferred locals when the type is obvious from the right-hand side (var sum = decimal + amount;). Skip var when 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 (BP BPXmlDocNoDocumentationComments).
  • 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
Install via CLI
npx skills add https://github.com/dynamics365ninja/d365fo-cli --skill xpp-class-and-method-rules
Repository Details
star Stars 12
call_split Forks 7
navigation Branch main
article Path SKILL.md
More from Creator
dynamics365ninja
dynamics365ninja Explore all skills →