web
You’re offline. This is a read only version of the page.
close
Skip to main content

Announcements

No record found.

News and Announcements icon
Community site session details

Community site session details

Session Id :
Dynamics 365 Community / Blogs / Hardik’s Dynamics Dojo / Understand Delegates in X++...

Understand Delegates in X++ the Right Way

HardikPatel523 Profile Picture HardikPatel523 230

“Stop Using CoC Everywhere!” — The Pizza Story That Finally Explains Delegates vs CoC in X++

A few days ago, one of the developers on my team was going through some standard code and suddenly paused.
He called me and asked:
“Hey… what is this delegate thing? And why is it just… sitting here doing nothing?” 🤨
I took a look — it was a clean delegate definition with no visible logic around it.
From his perspective, it looked like:
  • A method with no code
  • No direct call to any logic
  • Just… existing 😄
Naturally, the next question came:
“Why don’t we just use CoC for this? Why do we even need this?”
👉 And honestly… that’s a very valid question.
Because if you’re seeing delegates for the first time, they feel:
  • A bit confusing
  • Slightly unnecessary
  • And definitely mysterious
So instead of giving a textbook answer,
let’s break this down in the simplest way possible — with a pizza story you won’t forget 🍕

In real projects:

  • Multiple developers
  • Multiple extensions
  • Multiple integrations
👉 And suddenly:
  • Same method extended multiple times
  • Hidden logic via delegates
  • Order-dependent bugs
Result:
  • “It works on my machine” syndrome
  • Random production issues
  • Debugging = full investigation mode
It’s not about making the code work — it’s about choosing the right approach so others can extend it without breaking it.

Concept

👉 CoC (Chain of Command) = You control or modify behavior
👉 Delegate = You react to an event
Simple:
- CoC → control
- Delegate → notification

Real-Life Analogy (Pizza Shop)

You run a pizza shop

CoC = Changing the Recipe

VIP customer:
  • 👉 Add extra cheese 
  • 👉 Change preparation
  • 👉 You control how pizza is made

Delegate = Notification Bell

Order placed → bell rings 
  • SMS sent
  • Loyalty updated
  • Analytics tracked
👉 Everyone reacts independently

Technical Breakdown

Delegate

  • Event-like mechanism
  • Multiple subscribers
  • Can use:
    • `[SubscribesTo]`
    • `+=` (runtime)
  • No guaranteed execution order

CoC

  • Extends method
  • Uses `next`
  • Controls flow
  • Execution order matters

Example (Pizza System)

Base Class


Delegate using Event Handlers

SMS service

class PizzaSMSService
{
    [SubscribesTo(classStr(PizzaOrderProcessor), delegateStr(PizzaOrderProcessor, onOrderPlaced))]
    public static void sendSMS(str _orderId)
    {
        info("SMS sent for order: " + _orderId);
    }
}

Loyalty Service


class PizzaLoyaltyService
{
    [SubscribesTo(classStr(PizzaOrderProcessor), delegateStr(PizzaOrderProcessor, onOrderPlaced))]
    public static void addPoints(str _orderId)
    {
        info("Loyalty points added for order: " + _orderId);
    }
}

Delegate using += (Runtime Subscription)

Runnable Class

class PizzaDemoRun
{
    public static void main(Args _args)
    {
        PizzaOrderProcessor processor = new PizzaOrderProcessor();

        // Runtime subscriptions
        processor.onOrderPlaced += PizzaDemoRun::analyticsHandler;
        processor.onOrderPlaced += PizzaDemoRun::emailHandler;

        info("----- Normal Order -----");
        processor.processOrder("ORD001");

        info(" ");

        info("----- VIP Order -----");
        processor.processOrder("VIP");
    }

    public static void analyticsHandler(str _orderId)
    {
        info("Analytics recorded for order: " + _orderId);
    }

    public static void emailHandler(str _orderId)
    {
        info("Email sent for order: " + _orderId);
    }
}
👉 Now you have 4 subscribers total:
  • SMS (attribute)
  • Loyalty (attribute)
  • Analytics (`+=`)
  • Email (`+=`)

CoC Extension

[ExtensionOf(classStr(PizzaOrderProcessor))]
final class PizzaOrderProcessor_Extension
{
    public void preparePizza(str _orderId)
    {
        next preparePizza(_orderId);

        if (_orderId == "VIP")
        {
            info("Adding extra cheese for VIP order!");
        }
    }
}
👉 When `processOrder()` runs:
  • Delegate triggers ALL subscribers
  • Order is NOT guaranteed
  • CoC modifies behavior separately
👉 Output might look like:
Order received: ORD001
SMS sent...
Loyalty added...
Analytics recorded...
Email sent...
Preparing pizza...
Order completed...
👉 But order of handlers may change 😄

Comparison

SituationCoC Delegate 
Modify logic
Add listeners
Multiple subscribers⚠️ complex✅ easy
Execution orderControlled❌ unpredictable
CouplingTightLoose

Caution / Mistakes

Depending on Delegate Order

👉 Biggest trap
Never do: Handler A depends on Handler B
You’ll get random bugs

Misunderstanding `next`

Mandatory in CoC
Except `[Replaceable]`
👉 Golden Rule: If order matters → don’t use Delegate

Reality Check

👶 Junior - “CoC everywhere, done”
🧑‍💻 Senior - “Control or reaction?”
🧠 Architect - “Design extension points using delegates”

Debugging Tips

Delegates
👉 You’ll think: “Who called this???”
Reality:
  • Multiple handlers
  • Hidden subscriptions
Pro Tips
  • Search `SubscribesTo`
  • Check `+=` usage
  • Don’t assume order
💡 Honest truth:
Debugging delegates = chasing ghosts 👻
Debugging CoC = traffic analysis 🚗😄

My Personal Approach (What I Actually Do in Real Projects)

After working with CoC and Delegates in real scenarios, here’s what I personally prefer:
👉 I design my classes like this:
  • Add Delegate hooks (before/after key logic)
  • Add empty pre/post methods (for controlled CoC extension)
  • Add clear comments with usage examples
👉 Why?
Because I follow a simple principle from SOLID Principles: Open for Extension, Closed for Modification

👉 Translation in real developer language:
  • Don’t let people modify your logic ❌
  • Give them clean ways to extend it ✅

My Design Pattern

Whenever I write a core method, I structure it like this:

public void process()
{
    this.preProcess();        // CoC extension point

    onBeforeProcess();        // Delegate (notification)

    // Core logic

    onAfterProcess();         // Delegate (notification)

    this.postProcess();       // CoC extension point
}
👉 This gives:
  • Flexibility (Delegates)
  • Control (CoC)
  • Clean architecture

Real Example: SalesParmTable Cleanup Utility

Let’s say we are building a cleanup utility for SalesParmTable.

Base Class (Well-Designed)

class SalesParmCleanupService
{
    /// <summary>
    /// Delegate: Triggered before cleanup starts
    /// Use this for logging, validation, integrations
    /// Example:
    /// [SubscribesTo(classStr(SalesParmCleanupService), delegateStr(SalesParmCleanupService, onBeforeCleanup))]
    /// public static void handler(SalesParmTable _record)
    /// {
    ///     info("Before cleanup: " + _record.ParmId);
    /// }
    /// </summary>
    delegate void onBeforeCleanup(SalesParmTable _salesParmTable)
    {
    }

    /// <summary>
    /// Delegate: Triggered after cleanup completes
    /// Use this for notifications, audit, etc.
    /// </summary>
    delegate void onAfterCleanup(SalesParmTable _salesParmTable)
    {
    }

    public void cleanup(SalesParmTable _salesParmTable)
    {
        this.preCleanup(_salesParmTable);          // CoC

        onBeforeCleanup(_salesParmTable);          // Delegate

        this.deleteParmLines(_salesParmTable);     // Core logic

        onAfterCleanup(_salesParmTable);           // Delegate

        this.postCleanup(_salesParmTable);         // CoC
    }

    /// <summary>
    /// CoC extension point (controlled)
    /// </summary>
    protected void preCleanup(SalesParmTable _salesParmTable)
    {
    }

    /// <summary>
    /// CoC extension point (controlled)
    /// </summary>
    protected void postCleanup(SalesParmTable _salesParmTable)
    {
    }

    private void deleteParmLines(SalesParmTable _salesParmTable)
    {
        info("Deleting SalesParmTable record: " + _salesParmTable.ParmId);
    }
}

Delegate Subscriber Example

class SalesParmCleanupLogger
{
    [SubscribesTo(classStr(SalesParmCleanupService), delegateStr(SalesParmCleanupService, onAfterCleanup))]
    public static void logCleanup(SalesParmTable _salesParmTable)
    {
        info("Cleanup completed for: " + _salesParmTable.ParmId);
    }
}

CoC Extension Example

[ExtensionOf(classStr(SalesParmCleanupService))]
final class SalesParmCleanupService_Extension
{
    protected void preCleanup(SalesParmTable _salesParmTable)
    {
        next preCleanup(_salesParmTable);

        info("Validating before cleanup: " + _salesParmTable.ParmId);
    }
}

Why This Approach Works So Well

👉 Anyone extending your code gets:
  • Clear extension points
  • No need to modify base logic
  • No confusion about where to plug in
👉 And your future self gets:
  • Less debugging
  • Cleaner upgrades
  • Fewer “who wrote this???” moments 😄
Small but Powerful Habit
👉 Always add comments like:
  • Where to use Delegate
  • Where to use CoC
  • Example snippet
Because let’s be honest:
Developers don’t read documentation… 
but they do read comments when stuck 😄 

Don’t just write code that works today…
Write code that other developers can safely extend tomorrow without breaking it.

Comments