Delegates & Events in Dynamics 365 Finance & Operations
Question 10 — Loose coupling across models: how to declare, raise and subscribe to delegates.
Interview Question
Model Answer (Short)
A delegate is a special method (declared with the delegate keyword) that acts as a
publisher: it defines a contract (its parameter signature) but has an empty body.
Other classes — the subscribers — attach handler methods to it. When the delegate is called, all
subscribed handlers run. Delegates are the foundation of the event model and enable
loose coupling: the publishing model doesn't need to know anything about the subscribers, which is
essential when code lives in different models that can't reference each other directly. They avoid
over-layering and keep customizations upgrade-safe.
Detailed Explanation
Publisher — the delegate
- Declared with the
delegatekeyword and always has an empty body. - Its return type is always void; data is passed out via parameters (often an
EventHandlerResultobject). - Raised by simply calling it like a normal method inside the publishing class.
Subscriber — the handler
- A public static method whose signature matches the delegate.
- Attached either declaratively using the
[SubscribesTo(...)]attribute, or at runtime using+=and aneventHandlerexpression.
Why loose coupling matters
In D365 F&O, code is split across models and packages with a one-directional reference chain. A lower-level model cannot reference a higher-level one, so it can't call that code directly. By raising a delegate, the lower model publishes an extension point, and any higher model can subscribe to it — decoupling the two completely.
Prerequisites (Rule 5)
- Visual Studio with the Dynamics 365 developer tools.
- A custom model / package for your subscriber class.
- The subscriber method must be public static and match the delegate signature.
- Optionally, an
EventHandlerResult-style class to return data from subscribers.
Code Example — Declaring & raising a delegate
class AbcOrderProcessor
{
// 1. Declare the delegate (publisher) — empty body, void return
delegate void orderApprovedDelegate(SalesId _salesId, EventHandlerResult _result)
{
}
public void approveOrder(SalesId _salesId)
{
// ... approval logic ...
// 2. Raise the delegate — every subscriber will run
EventHandlerResult result = new EventHandlerResult();
this.orderApprovedDelegate(_salesId, result);
if (result.result())
{
info("A subscriber handled the approval event.");
}
}
}
Subscribing declaratively with [SubscribesTo]
class AbcOrderSubscriber
{
[SubscribesTo(classStr(AbcOrderProcessor),
delegateStr(AbcOrderProcessor, orderApprovedDelegate))]
public static void onOrderApproved(SalesId _salesId, EventHandlerResult _result)
{
info(strFmt("Order %1 was approved — running follow-up logic.", _salesId));
_result.result(true); // signal back to the publisher
}
}
Subscribing at runtime with +=
AbcOrderProcessor processor = new AbcOrderProcessor();
// Attach a handler dynamically
processor.orderApprovedDelegate += eventhandler(AbcOrderSubscriber::onOrderApproved);
processor.approveOrder('SO-0001');
Delegate vs. Method Wrapping (CoC)
| Aspect | Delegate / Event | Chain of Command |
|---|---|---|
| Coupling | Loose — publisher unaware of subscribers | Tighter — wraps a specific method |
| Direction across models | Higher model subscribes to lower model's event | Wraps a method in the referenced package |
| Return type | Always void; data via parameters | Matches the wrapped method |
| Number of listeners | Many subscribers can attach | One wrapper per model/object |
| Best for | Publishing extension points to other models | Altering the flow of a known method |
Points the interviewer wants to hear
- A delegate is a publisher with an empty body and a void return.
- Subscribers are public static methods matching the delegate signature.
- Attach via
[SubscribesTo](compile-time) or+=witheventhandler()(runtime). - Delegates return data through parameters like
EventHandlerResult. - They enable loose coupling across models that cannot reference each other directly.
Likely Follow-up Questions
- Why must a delegate always return void?
- How does a subscriber return a value to the publisher?
- What is the difference between
[SubscribesTo]and the+=approach? - How do delegates help when code lives in different models?
Key Takeaway
Delegates implement the publisher/subscriber pattern in X++, letting one model raise an event that any other model can subscribe to — without a direct reference. They keep customizations decoupled and upgrade-safe, and are the backbone of the D365 F&O event system.