Interfaces (F#)
As interfaces especificam os conjuntos de membros relacionados que outras classes implementam.
Sintaxe
// Interface declaration:
[ attributes ]
type [accessibility-modifier] interface-name =
[ interface ] [ inherit base-interface-name ...]
abstract member1 : [ argument-types1 -> ] return-type1
abstract member2 : [ argument-types2 -> ] return-type2
...
[ end ]
// Implementing, inside a class type definition:
interface interface-name with
member self-identifier.member1argument-list = method-body1
member self-identifier.member2argument-list = method-body2
// Implementing, by using an object expression:
[ attributes ]
let class-name (argument-list) =
{ new interface-name with
member self-identifier.member1argument-list = method-body1
member self-identifier.member2argument-list = method-body2
[ base-interface-definitions ]
}
member-list
Comentários
As declarações de interface se assemelham a declarações de classe, exceto que nenhum membro é implementado. Em vez disso, todos os membros são abstratos, conforme indicado pela palavra-chave abstract
. Você não fornece um corpo do método para métodos abstratos. O F# não pode definir uma implementação de método padrão em uma interface, mas é compatível com implementações padrão definidas pelo C#. Implementações padrão usando a palavra-chave default
só têm suporte ao herdar de uma classe base que não seja de interface.
A acessibilidade padrão para interfaces é public
.
Opcionalmente, você pode dar um nome a cada parâmetro de método usando a sintaxe F# normal:
type ISprintable =
abstract member Print: format: string -> unit
No exemplo ISprintable
acima, o método Print
tem um único parâmetro do tipo string
com o nome format
.
Há duas maneiras de implementar interfaces: usando expressões de objeto e usando tipos. Em ambos os casos, o tipo ou a expressão de objeto fornece corpos de método para métodos abstratos da interface. As implementações são específicas para cada tipo que implementa a interface. Portanto, os métodos de interface em tipos diferentes podem ser diferentes uns dos outros.
As palavras-chave interface
e end
, que marcam o início e o fim da definição, são opcionais quando você usa sintaxe leve. Se você não usar essas palavras-chave, o compilador tentará inferir se o tipo é uma classe ou uma interface analisando os constructos que você usa. Se você definir um membro ou usar outra sintaxe de classe, o tipo será interpretado como uma classe.
O estilo de codificação do .NET é iniciar todas as interfaces com um I
maiúsculo.
Você pode especificar vários parâmetros de duas maneiras: estilo F# e estilo .NET. Ambos serão compilados da mesma maneira para os consumidores do .NET, mas o estilo F# forçará os chamadores F# a usar o aplicativo de parâmetro de estilo F#, e o estilo .NET forçará os chamadores F# a usar o aplicativo de argumento de tupla.
type INumericFSharp =
abstract Add: x: int -> y: int -> int
type INumericDotNet =
abstract Add: x: int * y: int -> int
Implementar interfaces usando tipos de classe
Você pode implementar uma ou mais interfaces em um tipo de classe usando a palavra-chave interface
, o nome da interface e a palavra-chave with
, seguidas pelas definições de membro da interface, conforme mostrado no código a seguir.
type IPrintable =
abstract member Print: unit -> unit
type SomeClass1(x: int, y: float) =
interface IPrintable with
member this.Print() = printfn "%d %f" x y
As implementações de interface são herdadas, portanto, nenhuma classe derivada precisa reimplementá-las.
Chamar métodos de interface
Os métodos de interface só podem ser chamados por meio da interface, não por meio de qualquer objeto do tipo que implementa a interface. Portanto, talvez seja necessário fazer upcast para o tipo de interface usando o operador :>
ou o operador upcast
para chamar esses métodos.
Para chamar o método de interface quando você tiver um objeto de tipo SomeClass
, você deve atualizar o objeto para o tipo de interface, conforme mostrado no código a seguir.
let x1 = new SomeClass1(1, 2.0)
(x1 :> IPrintable).Print()
Uma alternativa é declarar um método no objeto que ativa e chama o método de interface, como no exemplo a seguir.
type SomeClass2(x: int, y: float) =
member this.Print() = (this :> IPrintable).Print()
interface IPrintable with
member this.Print() = printfn "%d %f" x y
let x2 = new SomeClass2(1, 2.0)
x2.Print()
Implementar interfaces usando expressões de objeto
As expressões de objeto fornecem uma maneira breve de implementar uma interface. Elas são úteis quando você não precisa criar um tipo nomeado e só quer um objeto que dê suporte aos métodos de interface, sem nenhum método adicional. Uma expressão de objeto é ilustrada no código a seguir.
let makePrintable (x: int, y: float) =
{ new IPrintable with
member this.Print() = printfn "%d %f" x y }
let x3 = makePrintable (1, 2.0)
x3.Print()
Herança de interface
Uma interface pode herdar de uma ou mais interfaces base.
type Interface1 =
abstract member Method1: int -> int
type Interface2 =
abstract member Method2: int -> int
type Interface3 =
inherit Interface1
inherit Interface2
abstract member Method3: int -> int
type MyClass() =
interface Interface3 with
member this.Method1(n) = 2 * n
member this.Method2(n) = n + 100
member this.Method3(n) = n / 10
Implementar interfaces com implementações padrão
O C# dá suporte à definição de interfaces com implementações padrão, da seguinte forma:
using System;
namespace CSharp
{
public interface MyDim
{
public int Z => 0;
}
}
Elas são diretamente consumidas de F#:
open CSharp
// You can implement the interface via a class
type MyType() =
member _.M() = ()
interface MyDim
let md = MyType() :> MyDim
printfn $"DIM from C#: %d{md.Z}"
// You can also implement it via an object expression
let md' = { new MyDim }
printfn $"DIM from C# but via Object Expression: %d{md'.Z}"
Você pode substituir uma implementação padrão com override
, como substituiria qualquer membro virtual.
Todos os membros em uma interface que não têm uma implementação padrão devem ainda ser implementados explicitamente.
Implementar a mesma interface em instanciações genéricas diferentes
O F# dá suporte à implementação da mesma interface em instanciações genéricas diferentes, da seguinte forma:
type IA<'T> =
abstract member Get : unit -> 'T
type MyClass() =
interface IA<int> with
member x.Get() = 1
interface IA<string> with
member x.Get() = "hello"
let mc = MyClass()
let iaInt = mc :> IA<int>
let iaString = mc :> IA<string>
iaInt.Get() // 1
iaString.Get() // "hello"