Interfaces
interface declarations describe a contract that other types may implement. Interfaces are reference types; they emit as abstract CLR interfaces and cannot be instantiated directly.
interface ILogger {
func Log(message: string) -> ()
}
Interfaces may be declared at the top level, inside namespaces, or nested inside other types. Like classes, they support the same set of member declarations (methods, properties, indexers, and nested types). Instance members are abstract requirements by default, but supplying a body for a method or accessor turns it into a default implementation emitted directly on the interface.
Static members, by contrast, must provide a body and emit as real static members on the interface type; implementing types never participate in their implementation or override process. When an interface member uses accessors, a bare ; accessor denotes an unimplemented accessor requirement (get;/set;).
Member accessibility
Interface members default to public, including members with default bodies.
Explicit public, private, protected, internal, protected internal, and
private protected access modifiers retain their ordinary meaning, following C#.
Private function members must have implementations and cannot be abstract or virtual.
Instance default implementations are implicitly virtual unless private or sealed.
These rules apply to methods and property, indexer, and event accessors.
Implicit interface implementation requires public members on both the interface and the implementing type, including required property accessors. Non-public requirements use explicit interface implementations. Private default helpers are not implementation requirements and do not become class virtual slots.
Base interfaces
An interface may inherit from other interfaces by listing them after a colon.
Every base must be an interface. The base list is resolved in the declared interface's
scope, including its own generic type parameters, also for nested declarations.
For example, interface Child<T> : Parent<T> {} passes Child's T to Parent.
interface IAsyncLogger : ILogger, IDisposable {}
Implementing interfaces
Classes, structs, and union carriers implement interfaces by listing them in their base list. The optional class base (if any) must appear first, followed by one or more interfaces. Structs and unions accept interfaces only. Implementing types must provide members whose signatures match every required interface member—name, parameter count, parameter types (including by-reference modifiers), and return type must align.
class FileLogger : ILogger, IDisposable {
func Dispose() -> () { /* release resources */ }
func Log(message: string) -> () {
Console.WriteLine(message)
}
}
For a union, the interface is implemented by the carrier rather than by each generated case type:
union LogFailure: ILogger {
case Unavailable
func Log(message: string) -> () {
Console.WriteLine(message)
}
}
An explicit interface implementation qualifies the member name with the
interface type: ILogger.Log. Explicit members are always instance members,
ignore virtual and override, and are accessible only through the interface.
The qualifier must name an interface (RAV0313), the containing type must list
that interface (RAV0314), and the interface must contain a member with the
same name and signature (RAV0315).
class QuietLogger : ILogger {
func ILogger.Log(message: string) {
Console.WriteLine("[quiet] " + message)
}
}
let logger = QuietLogger()
logger.Log("hi") // error: member not found
(logger :> ILogger).Log("hi") // ok
Reference-only variance conversions
Generic interface out and in annotations admit identity or reference conversions
between the corresponding type arguments. Boxing is not a variance conversion:
Producer<int> cannot become Producer<object>, and Consumer<object> cannot
become Consumer<int>. Identical value arguments remain valid. An unconstrained
type parameter does not establish the required reference conversion.
Nullable reference arguments obey the same variance direction while preserving
nullability. Producer<string?> can widen to Producer<object?>, and
Consumer<object?> can narrow to Consumer<string?>. Variance cannot remove a
possible null: Producer<string?> cannot implicitly become Producer<object>,
and Consumer<object> cannot become Consumer<string?>. Nullable value types do
not become reference arguments through this rule. Runtime generic arguments and
emitted interface variance annotations are unchanged.