CLOUDCLIF – Blogs
  • Home
  • D365 F&O
  • Work Flows
  • Reports
  • Enterprise Portal
    D365 F&O

    Delegates in X++: The Underused Pattern for Clean Extensibility 

    by Muhammad Salman Siddiqui April 30, 2026

    Reading time: ~8 minutes. 

    Delegates are the cleanest extension mechanism in X++. They are also the least taught. Most F&O developers reach for CoC or event handlers first, write working code, and never seriously consider whether a delegate would have been the better answer — or whether the situation called for publishing one of their own. This post is about both halves: subscribing to delegates that already exist, and writing delegates in your own code so other extensions can plug in cleanly. 

    If you haven’t read the foundation posts: CoC mechanics Chain of Command in D365 F&O, events vs CoC Event Handlers vs Chain of Command. 

    What a delegate actually is 

    A delegate in X++ is a method declaration whose body is empty by design. The publisher of the delegate isn’t writing logic — they’re declaring an extension point. When code execution reaches the call site, the delegate fires, every subscriber’s handler runs, and execution continues. 

    Microsoft Learn’s Events and delegates reference shows the canonical shape: 

    // In the publisher class — declares the extension point 
    delegate void RentalTransactionAboutTobeFinalizedEvent( 
        FMRental fmRentalRecord, struct RentalConfirmation) 
    { 
    } 
    // In a subscriber class — handles the event when it fires 
    [SubscribesTo( 
        classstr(FMRentalCheckoutProcessor), 
        delegatestr(FMRentalCheckoutProcessor, RentalTransactionAboutTobeFinalizedEvent))] 
    
    public static void RentalFinalizedEventHandler( 
        FMRental rentalRecord, Struct rentalConfirmation) 
    { 
        // your custom logic runs when the publisher fires the delegate 
    } 

    Two non-obvious things on that snippet. First, the delegate’s body is empty — that’s not a stub, it’s the entire definition. Second, delegates always have void return type. Microsoft is explicit on this in the migration-delegates reference: “delegates do not have a return value, an EventHandlerResult is passed as a parameter to provide access to the needed result value after the delegate has returned.” That void-only rule shapes everything about how delegates carry information back to their caller — covered in the next section. 

    EventHandlerResult — how a delegate returns a value without returning a value 

    Because delegates can’t return values directly, the publisher passes a result-carrier object as a parameter. Subscribers populate it; the publisher reads it after the delegate fires. The pattern is documented across Microsoft Learn’s Respond by using EventHandlerResult and EventHandlerResult classes references.

    The carrier comes in three flavours, all from the platform: 

    • EventHandlerResult — generic; subscriber calls _result.result(value) to set whatever value type matches the agreed contract. 
    • EventHandlerAcceptResult — restricted to accept-only signalling; subscriber calls _result.accept(). You cannot reject through this type. 
    • EventHandlerRejectResult — the inverse, restricted to _result.reject(). You cannot accept. 

    Why three? Because delegates can have multiple subscribers, and one subscriber can overwrite another’s result. Microsoft Learn’s EventHandlerResult page describes the problem directly: “If the response is gathered by using an EventHandlerResult object, the second subscriber that validates and replies with Boolean true might overwrite the Boolean false from the first subscriber.” The accept-only and reject-only types narrow what each subscriber can express, which makes the multi-subscriber case behave more predictably. 

    There’s also a stricter mechanism: EventHandlerResult::newSingleResponse(). When the publisher instantiates the result this way, the framework throws an exception as soon as a second subscriber tries to provide a result. This is the right choice when the publisher genuinely needs at most one subscriber to answer — overlapping ISVs becomes a fail-fast scenario instead of a silent overwrite. 

    Three legitimate uses of delegates 

    Delegates are general-purpose, but in practice they get used for three distinct things. Recognizing which one you’re doing matters because the contract you publish should match the intent. 

    1. Notify-only — “this just happened”. The publisher fires the delegate to tell anyone listening that something occurred. No response expected. No EventHandlerResult parameter. Subscribers can do whatever they want — log it, push it elsewhere, ignore it. This is the cleanest contract: the publisher is genuinely indifferent to subscriber behaviour. 

    2. Request-and-respond — “someone please answer this”. The publisher needs information that subscribers might supply. The delegate signature includes an EventHandlerResult-family parameter. The publisher reads the result after the delegate fires. This is the pattern in Microsoft Learn’s InventWarehouseEntity validateWarehouseTypeDelegate example. Use this when the answer might depend on logic the publisher can’t reasonably know. 

    3. Request-and-reject — “please veto this if you have a reason”. Specialized version of #2 using EventHandlerRejectResult. The publisher proceeds unless a subscriber rejects. Useful for plug-in validation: any subscriber can block, none has to say yes. 

    The pattern Microsoft tells you not to write 

    Microsoft Learn’s EventHandlerResult page has an unusually direct warning, with a code example labelled “This example is an example of code that you should not write.” Worth reading in full because it’s the single most-broken delegate pattern on real projects. 

    Microsoft’s exact words: “Before the subscribing logic responds, it should not evaluate whether the result object parameter already contains a result.” 

    Rephrased: don’t write a subscriber that does “if some other subscriber already answered, I’ll skip”. Don’t write one that does “if no one else answered, I’ll provide a default”. Either of those couples your subscriber to the assumed behaviour of other subscribers, which you don’t control and shouldn’t reason about. 

    The correct shape: each subscriber answers if and only if its own conditions are met. Each subscriber’s logic stays self-contained. The framework — or the newSingleResponse constructor — handles what happens when multiple subscribers try to answer. Don’t write defensive code against the multi-subscriber case in your own subscriber. That’s the framework’s responsibility. 

    When to publish your own delegate 

    Most discussion of delegates focuses on subscribing — finding a Microsoft delegate that fires near the point you need to extend, and hooking into it. But the more interesting question is when to publish one in your own code. 

    Publish a delegate when: 

    • You’re writing code that other extensions will want to plug into. ISV solutions, partner-published frameworks, internal libraries used across multiple projects — all need explicit extension points. 
    • The right extension point is in the middle of a method, not before or after it. CoC and Pre/Post handlers can only wrap whole methods; if subscribers need to intervene at a specific point inside the method, a delegate fired from that point is the only clean answer. 
    • You want the contract to be discoverable. A published delegate shows up in tooling and code searches as an explicit extension point, more visible than an internal method waiting for someone to CoC it. 
    • You want forward-compatibility. Microsoft can change a method’s internal logic across versions; a delegate fired from inside that method is a stable contract you’ve made with subscribers, decoupled from the implementation around it. 
    • Don’t publish a delegate when: 
    • There’s already a Microsoft delegate firing close to the same point. Two delegates means subscribers have to know which to subscribe to. 
    • The right answer is a method override. If subscribers genuinely need to replace your method’s behaviour, CoC on a public method is the cleaner path. 
    • You’re tempted to fire one as a notification just so subscribers can read state. Refactor that state into a public read-only property instead. 

    3 tricky interview questions (with verified answers) 

    Q1. A delegate has the signature delegate void myDelegate(int x, EventHandlerResult result). Two subscribers respond. What value does the publisher see? 

    Whatever the second subscriber set — last write wins. Microsoft Learn’s EventHandlerResult page is explicit: “the EventHandlerResult class can only contain a single result. If multiple subscribers provide their individual results, the last respondent wins and overwrites the results from the previous subscribers.” Worse, you can’t predict which subscriber runs second — there’s no guaranteed sequence. “There is not a defined order in the processing of handler methods”, per Microsoft’s delegates-migration reference. The fix is either to design the contract so only one subscriber can plausibly respond (use EventHandlerAcceptResult or EventHandlerRejectResult to constrain the response), or use EventHandlerResult::newSingleResponse() to get a fail-fast exception when more than one subscriber answers. 

    Q2. Can a delegate have a non-void return type? 

    No. “Delegates in X++ must have the return type void” — Docentric’s delegates and event handlers writeup states this directly, and Microsoft Learn’s solve-dependencies-with-delegates reference confirms “due to the fact that delegates do not have a return value, an EventHandlerResult is passed as a parameter to provide access to the needed result value.” A candidate who tries to declare delegate boolean myDelegate(…) is showing they haven’t actually written a delegate before. The right pattern is the EventHandlerResult parameter. 

    Q3. Should a delegate handler check whether result.result() is already populated before responding? 

    No, and Microsoft Learn explicitly labels this as bad practice. The exact words: “Before the subscribing logic responds, it should not evaluate whether the result object parameter already contains a result.” Each subscriber should answer based on its own conditions, independently of whether other subscribers have answered. Coupling your subscriber to other subscribers’ behaviour is a fragile dependency you don’t control. The framework manages multi-subscriber contention via EventHandlerAcceptResult, EventHandlerRejectResult, or newSingleResponse. A senior candidate adds: if a delegate handler in production code is doing this check, it’s a signal the contract is ill-defined and should be redesigned, not patched. 

    What to actually do 

    1. Before reaching for CoC on an internal Microsoft method, search the area for an existing delegate. Microsoft has published many in posting, validation, and pricing flows specifically to provide cleaner extension points. Right-click a class name in Visual Studio and use “Find references” to surface delegates and SubscribesTo handlers in that area. 
    1. When you do subscribe to a delegate, write each handler self-contained. Don’t check whether other subscribers already answered. Don’t provide a default for the multi-subscriber case. Each handler responds based on its own conditions only. 
    1. When the contract genuinely needs at most one subscriber, use EventHandlerResult::newSingleResponse() in the publisher. Fail-fast on contention beats silent overwrites. 
    1. When publishing your own delegates in framework or ISV code, prefer EventHandlerAcceptResult or EventHandlerRejectResult over generic EventHandlerResult when the response is binary. The narrower type prevents whole classes of multi-subscriber confusion. 
    1. Treat published delegates as part of your public API. Renaming, signature changes, or removal will break every subscriber. The same compatibility discipline you’d apply to a public class method applies. 

    References 

    • Microsoft Learn — Events and delegates (learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/dev-ref/xpp-events). Authoritative reference for delegate syntax, the SubscribesTo attribute, and the FMRentalCheckoutProcessor example used above. Last updated February 2025. 
    • Microsoft Learn — Respond by using EventHandlerResult (learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/extensibility/respond-event-handler-result). Source for the explicit “do not write this” warning and the InventWarehouseEntity validateWarehouseTypeDelegate example. Last updated May 2025. 
    • Microsoft Learn — EventHandlerResult classes in request or response scenarios (learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/dev-tools/event-handler-result-class). Source for EventHandlerAcceptResult, EventHandlerRejectResult, and newSingleResponse. Documents the last-write-wins behaviour. 
    • Microsoft Learn — Solve dependencies among models by using delegates during code migration (learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/migration-upgrade/delegates-migration). Source for the void-return-type rule and the no-defined-order statement on subscriber processing. 
    • Docentric — Delegates and Event Handlers in D365FO (ax.docentric.com/delegates-and-event-handlers-in-d365fo/). Best community-level explanation of when to use delegates vs other extension mechanisms; covers EventHandlerResult patterns with current syntax. Updated February 2026. 

    Next up in this series: Best Practice Checks — Fighting the Warnings You’ll Actually See in Every Build. [PASTE DAY 7 URL HERE] 

    April 30, 2026 0 comments
    0 FacebookTwitterPinterestLinkedinWhatsappEmail
  • D365 F&O

    Table Extensions vs CoC on Tables: Picking the Right Tool for Adding Fields and Logic 

    by Muhammad Salman Siddiqui April 30, 2026
    April 30, 2026

    Reading time: ~9 minutes.  Most table customization in D365 F&O is one of two things: you’re adding a field, or you’re changing how the table behaves on insert, update, validate, or delete. The decision sounds simple — table …

    0 FacebookTwitterPinterestLinkedinWhatsappEmail
  • D365 F&O

    Form Extensions in D365 F&O: Data Sources, Controls, and the Gotchas Nobody Warns You About 

    by Muhammad Salman Siddiqui April 29, 2026
    April 29, 2026

    Reading time: ~9 minutes.  Form extensions are where most F&O developers stop reading the Microsoft Learn docs too early. The syntax looks straightforward — slap [ExtensionOf] on a class, name …

    0 FacebookTwitterPinterestLinkedinWhatsappEmail
  • D365 F&O

    The 5 X++ Extension Pitfalls I See on Almost Every D365 F&O Project 

    by Muhammad Salman Siddiqui April 27, 2026
    April 27, 2026

    Reading time: ~7 minutes.  Days 1 and 2 on this blog laid out the mechanics of Chain of Command and event handlers. This post is about what goes wrong anyway. …

    0 FacebookTwitterPinterestLinkedinWhatsappEmail
  • Uncategorized

    Event Handlers vs Chain of Command: The Real Decision Framework for 2026

    by Muhammad Salman Siddiqui April 27, 2026
    April 27, 2026

    Reading time: ~9 minutes.  Yesterday’s post argued that Chain of Command is usually the right tool for overriding F&O method logic. That was deliberately one-sided — you can’t teach a decision framework …

    0 FacebookTwitterPinterestLinkedinWhatsappEmail
  • Uncategorized

    Chain of Command in D365 F&O: A Practitioner’s Guide to When (and When Not) to Use It

    by Muhammad Salman Siddiqui April 26, 2026
    April 26, 2026

    Reading time: ~8 minutes.  Every D365 Finance & Operations developer writes Chain of Command code. Most write it wrong for the first six months. Not broken wrong — the code compiles and …

    0 FacebookTwitterPinterestLinkedinWhatsappEmail
  • D365 F&O

    Designing a Scalable Multi-Source Order Processing Cycle in Dynamics 365 Finance & Operations

    by Muhammad Salman Siddiqui February 16, 2026
    February 16, 2026

    Introduction  Modern enterprises rarely operate with a single order intake channel. Orders can originate from web stores, partner systems, EDI networks, or even file-based submissions such as Excel. The real challenge for organizations is …

    0 FacebookTwitterPinterestLinkedinWhatsappEmail
Load More Posts

Recent Posts

  • Delegates in X++: The Underused Pattern for Clean Extensibility 
  • Table Extensions vs CoC on Tables: Picking the Right Tool for Adding Fields and Logic 
  • Form Extensions in D365 F&O: Data Sources, Controls, and the Gotchas Nobody Warns You About 
  • The 5 X++ Extension Pitfalls I See on Almost Every D365 F&O Project 
  • Event Handlers vs Chain of Command: The Real Decision Framework for 2026

Recent Comments

No comments to show.

About Me

About Me

Writer & Reader

Muhammad Salman Siddiqui

Technical Delivery Lead for Dynamics 365 Finance & Operations | Solution Architect

A consistent, meritorious and motivated professional looking forward to a challenging career in an environment that brings the best out of people while developing them personally and professionally.

read more

Keep in touch

Linkedin Email

Recent Posts

  • Delegates in X++: The Underused Pattern for Clean Extensibility 

    April 30, 2026
  • Table Extensions vs CoC on Tables: Picking the Right Tool for Adding Fields and Logic 

    April 30, 2026
  • Form Extensions in D365 F&O: Data Sources, Controls, and the Gotchas Nobody Warns You About 

    April 29, 2026
  • The 5 X++ Extension Pitfalls I See on Almost Every D365 F&O Project 

    April 27, 2026
  • Event Handlers vs Chain of Command: The Real Decision Framework for 2026

    April 27, 2026

Categories

  • D365 F&O (5)
  • Uncategorized (2)

About me

banner

Muhammad Salman Siddiqui

Technical Delivery Lead for Dynamics 365 Finance & Operations | Solution Architect

A consistent, meritorious and motivated professional looking forward to a challenging career in an environment that brings the best out of people while developing them personally and professionally.

read more

Popular Posts

  • 1

    Designing a Scalable Multi-Source Order Processing Cycle in Dynamics 365 Finance & Operations

    February 16, 2026
  • Chain of Command in D365 F&O: A Practitioner’s Guide to When (and When Not) to Use It

    April 26, 2026
  • Event Handlers vs Chain of Command: The Real Decision Framework for 2026

    April 27, 2026
  • Linkedin

@2026 - All Right Reserved. Designed and Developed by CLOUDCLIF


Back To Top
CLOUDCLIF – Blogs
  • Home
  • D365 F&O
  • Work Flows
  • Reports
  • Enterprise Portal