DynamicInterface

The DynamicInterface aspect allows the declaration of concrete set of members to be available on a Dynamic type.

By default, a dynamic type allows any member (method or property) access at compile time, and calls will be dispatched dynamically at runtime; if a member does not exist, the call will fail at runtime. Values returned from dynamic access are treated as dynamic themselves.

In cases where the exact set of members of a type can be known, the type can be cast to an dynamic interface. Moving forward, the compiler will know what members are supported, only allow known calls, and – of course – know the concrete result types of methods and properties.

As a result, code can be written against the dynamic interface that is much more type safe.

Of course, cast is valid – i.e. if the concrete object actually does implement the matching methods – is still only determined until runtime.

Declaring a Dynamic Interface

An interface (or protocol, in Swift lingo) can be declared as dynamic by applying the DynamicInterface aspect to it. The aspect has one mandatory parameter, and that is a concrete dynamic bridge type (i.e. a type implementing the IDynamicObject).

Right now, Elements ships with only one pre-defined bridge type, and, that is EcmascriptObject which implements access to the DOM and Node.js object model on WebAssembly, but it is possible for users to implement their own.

[DynamicInterface(typeof(EcmascriptObject))]
IDOMHtmlElement = public interface(IDOMElement)
   property tabindex: Integer;
   method focus;
   method click;
end;
[DynamicInterface(typeof(EcmascriptObject))]
public interface IDOMHtmlElement : IDOMElement
{
   int tabindex { get; }
   void focus();
   void click();
}
@DynamicInterface(typeof(EcmascriptObject))
public protocol IDOMHtmlElement : IDOMElement
{
   var tabindex { get }
   func focus()
   func click()
}
@DynamicInterface(typeof(EcmascriptObject))
public interface IDOMHtmlElement : IDOMElement
{
   int tabindex { __get; }
   void focus();
   void click();
}
<DynamicInterface(typeof(EcmascriptObject))>
Public Interface IDOMHtmlElement 
  Inherits IDOMElement
  
  Property tabindex As Integer
  Sub focus()
  Sub click()
End Interface;

Any dynamic object (backed by the matching bridge) can be cast to such an interface, and the compiler will try to dispatch the the member access dynamically as they are called; calls can still fail, if the concrete object did not match the interface.

Additionally, a more concrete runtime type name can be provided as second parameter to the interface:

[DynamicInterface(typeof(EcmascriptObject), 'Body')]
IDOMBodyHtmlElement = public interface(IDOMHtmlElement)
   ...
end;
[DynamicInterface(typeof(EcmascriptObject), "Body")]
public interface IDOMBodyHtmlElement : IDOMHtmlElement
{
   ...
}
@DynamicInterface(typeof(EcmascriptObject), "Body")
public protocol IDOMBodyHtmlElement : IDOMHtmlElement
{
   ...
}
@DynamicInterface(typeof(EcmascriptObject), "Body")
public interface IDOMBodyHtmlElement : IDOMHtmlElement
{
   ...
}
<DynamicInterface(typeof(EcmascriptObject), "Body")>
Public Interface IDOMBodyHtmlElement 
  Inherits IDOMHtmlElement
  
  ...
End Interface;

In this case, the interface cast will be checked immediate at runtime, to see of the underlying dynamic object matches the specified type name.

Of course, calls could still fail if the object does not actually provide all members as specified by the interface. It is up to the developer declaring the interface to ensure its correctness.

See Also