Snippet support
This unit describes the snippet and code that you can use to create interfaces in AL. Additionally, the following sections describe the tinterface snippet and use it to build an example of creating and using an interface in AL.
An interface in AL is similar to an interface in any other programming language; it's a syntactical contract that can be implemented by a nonabstract method. The interface is used to define which capabilities must be available for an object, while allowing actual implementations to differ, as long as they comply with the defined interface.
This feature allows for writing code that reduces the dependency on implementation details. It makes it easier to reuse code, and supports a polymorphic way of calling object methods, which again can be used for substituting business logic.
The interface declares an interface name along with its methods, and codeunits that implement the interface methods, must use the implements keyword along with one or more interface names. The interface itself doesn't contain any code, only signatures, and can't itself be called from code, but must be implemented by other objects.
The AL compiler checks to ensure that implementations adhere to assigned interfaces. You can declare variables as a given interface to allow passing objects that implement the interface, and then call interface implementations on the passed object in a polymorphic manner.
Since Business Central 2023 release wave 1, you can use the Go to Implementations option in the Visual Studio Code context menu (or press Ctrl+F12) on an interface to view all the implementations within scope for that interface. This is supported on interfaces, and on codeunits and enums, which implement an interface, as well as on their procedures if they map to a procedure on an interface. It's also supported on codeunit variables of type interface to jump to other implementations of that specific interface.
Tinterface snippet
Typing the shortcut tinterface creates the basic layout for an interface object when you are using the AL Language extension in Visual Studio Code.
Interface example
The following example defines an IAddressProvider interface, which has one getAddress method with a certain signature.
interface IAddressProvider
{
procedure GetAddress(): Text;
}
The CompanyAddressProvider and PrivateAddressProvider codeunits implement the IAddressProvider interface, and each defines a different implementation of the getAddress method, which in this case is a simple variation of address value.
codeunit 65100 CompanyAddressProvider implements IAddressProvider
{
procedure GetAddress(): Text;
begin
exit('Company address \ Denmark 2800')
end;
}
codeunit 65101 PrivateAddressProvider implements IAddressProvider
{
procedure GetAddress(): Text;
begin
exit('My Home address \ Denmark 2800')
end;
}
MyAddressPage is a simple page with an action that captures the choice of address and calls, based on that choice, an implementation of the IAddressProvider interface.
page 65100 MyAddress
{
PageType = Card;
ApplicationArea = All;
UsageCategory = Administration;
layout
{
area(Content)
{
group(GroupName)
{
}
}
}
actions
{
area(Processing)
{
action(WhatsTheAddress)
{
ApplicationArea = All;
Caption = 'What's the Address?';
ToolTip = 'Select the address.';
Image = Addresses;
trigger OnAction()
var
iAddressProvider: Interface IAddressProvider;
begin
AddressproviderFactory(iAddressProvider);
Message(iAddressProvider.GetAddress());
end;
}
action(SendToHome)
{
ApplicationArea = All;
Image = Home;
Caption = 'Send to Home.';
ToolTip = 'Set the interface implementation to Home.';
trigger OnAction()
begin
sendTo := sendTo::Private
end;
}
action(SendToWork)
{
Image = WorkCenter;
Caption = 'Send to Work.';
ToolTip = 'Set the interface implementation to Work.';
ApplicationArea = All;
trigger OnAction()
begin
sendTo := sendTo::Company
end;
}
}
}
local procedure AddressproviderFactory(var iAddressProvider: Interface IAddressProvider)
var
CompanyAddressProvider: Codeunit CompanyAddressProvider;
PrivateAddressProvider: Codeunit PrivateAddressProvider;
begin
if sendTo = sendTo::Company then
iAddressProvider := CompanyAddressProvider;
if sendTo = sendTo::Private then
iAddressProvider := PrivateAddressProvider;
end;
var
sendTo: enum SendTo;
}
Extensible enums also support implementing an interface. You can assign the enum value to an interface variable that can initialize the interface. Then, you can use the enum to choose the specific implementation to use for a given interface. The SendTo enum currently holds two values: Company and Private.
enum 65100 SendTo
{
Extensible = true;
value(0; Company)
{
}
value(1; Private)
{
}
}
Obsolete support
Interfaces can be obsoleted like other AL object types by using the ObsoleteState, ObsoleteReason, and ObsoleteTag properties and the Obsolete attribute. This approach can help with the process of managing interface change.
Handle removed enum extension values
You can use enums to select an interface implementation. However, because of the pluggable nature, an enum extension and its corresponding interface implementation could have been uninstalled from the tenant, while the setting for the enum is kept, which will now point to an unknown value. Instead of having an app code add the validation logic for handling this scenario, the platform should catch when a nonvalid enum value is provided and allow for an extensible way to handle that issue. This scenario is related to, but not the same as, fallback to a default value that is used when no enum value is provided.
The UnknownValueImplementation property specifies the implementers that are used for ordinal values that aren't included in the defined list of enum values.
UnknownValueImplementation = Interface = InterfaceImplementation;
The following example illustrates different implementations of the IFoo interface. SomeEnum has UnknownValueImplementation set to catch the case where some extension uses an unknown enum value.
pageextension 50130 CustListExt extends "Customer List"
{
trigger OnOpenPage()
var
ifoo: Interface IFoo;
e: enum SomeEnum;
begin
e := SomeEnum::Yes;
ifoo := e;
ifoo.Foo(); // => YesFooImpl specified in Implementation on Yes value
e := SomeEnum::No;
ifoo := e;
ifoo.Foo(); // => DefaultFooImpl specified in DefaultImplementation
e := 2; // No enum value matches this.
ifoo := e;
ifoo.Foo(); // => UnknownFooImpl specified in UnknownImplementation
end;
}
interface IFoo
{
procedure Foo();
}
codeunit 50145 ErrorFooImpl implements IFoo
{
procedure Foo()
begin
Message('Bad FOO')
end;
}
codeunit 50146 DefaultFooImpl implements IFoo
{
procedure Foo()
begin
Message('Default FOO')
end;
}
codeunit 50147 YesFooImpl implements IFoo
{
procedure Foo()
begin
Message('Yes FOO')
end;
}
enum 50135 SomeEnum implements IFoo
{
Extensible = true;
UnknownValueImplementation = IFoo = ErrorFooImpl;
DefaultImplementation = IFoo = DefaultFooImpl;
value(0; Yes)
{
Implementation = IFoo = YesFooImpl;
}
value(1; No)
{
// Nothing specified. Using defaults
}
}
The UnknownValueImplementation property applies to enums. Uninstalling enum extensions can result in persisted values becoming unknown. The UnknownValueImplementation provides a generic error handling in such cases. Enums are often used to select an interface implementation. However, because of the nature of an extensible development model, an enum extension and its corresponding interface implementation can be uninstalled from a tenant, while the value for the specific enum is still available, which will now point to an unknown value. Using the UnknownValueImplementation property when defining an enum prevents the throwing of a technical error message in the UI and allows for a more user-friendly error handling.
Related to UnknownValueImplementation is the DefaultImplementation property, which is used for fallback to a default value when no enum value is provided.