接口 (F#)

接口指定其他类实现的相关成员集。

语法

// 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

备注

接口声明类似于类声明,不同之处在于没有实现任何成员。 相反,所有成员都是抽象的,如关键字 abstract 所指示。 不为抽象方法提供方法主体。 F# 不能在接口上定义默认方法实现,但它与 C# 定义的默认实现兼容。 只有在从非接口基类继承时,才支持使用 default 关键字的默认实现。

接口的默认可访问性为 public

可以选择使用普通 F# 语法为每个方法参数指定一个名称:

type ISprintable =
    abstract member Print: format: string -> unit

在以上 ISprintable 示例中,Print 方法具有类型为 string 的单个参数,其名称为 format

有两种方法可以实现接口:使用对象表达式和使用类型。 无论采用哪种方法,类型或对象表达式都为接口的抽象方法提供方法主体。 实现特定于实现接口的每个类型。 因此,不同类型的接口方法可能彼此不同。

使用轻型语法时,关键字 interfaceend(用于标记定义的开始和结尾)是可选的。 如果不使用这些关键字,编译器会尝试通过分析使用的构造来推断类型是类还是接口。 如果定义成员或使用其他类语法,则类型将被解释为类。

.NET 编码样式是所有接口都以大写 I 开头。

可以通过两种方式指定多个参数:F# 样式和 .NET 样式。 对于 .NET 使用者,这两种方法的编译方式相同,但 F# 样式将强制 F# 调用方使用 F# 样式的参数应用程序,而 .NET 样式将强制 F# 调用方使用元组参数应用程序。

type INumericFSharp =
    abstract Add: x: int -> y: int -> int

type INumericDotNet =
    abstract Add: x: int * y: int -> int

使用类类型实现接口

可以使用 interface 关键字、接口名称和 with 关键字,后跟接口成员定义,在类类型中实现一个或多个接口,如以下代码所示。

type IPrintable =
    abstract member Print: unit -> unit

type SomeClass1(x: int, y: float) =
    interface IPrintable with
        member this.Print() = printfn "%d %f" x y

接口实现是会受到继承的,因此任何派生类都不需要重新实现它们。

调用接口方法

接口方法只能通过接口调用,不能通过实现接口的类型的任何对象调用。 因此,可能需要使用 :> 运算符或 upcast 运算符向上转换到接口类型,才能调用这些方法。

若要在对象类型为 SomeClass 时调用接口方法,必须将对象向上转换到接口类型,如以下代码所示。

let x1 = new SomeClass1(1, 2.0)
(x1 :> IPrintable).Print()

一种替代方法是在对象上声明一个方法,该方法会向上转换并调用接口方法,如以下示例所示。

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()

使用对象表达式实现接口

对象表达式提供了实现接口的简便方法。 当无需创建命名类型,并且只是需要一个支持接口方法的对象,无需任何其他方法时,对象表达式就非常有用。 下面的代码演示了对象表达式。

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()

接口继承

接口可从一个或多个基接口继承。

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

使用默认实现实现接口

C# 支持使用默认实现定义接口,如下所示:

using System;

namespace CSharp
{
    public interface MyDim
    {
        public int Z => 0;
    }
}

可以直接从 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}"

可以使用 override 覆盖默认实现,就像覆盖任何虚拟成员一样。

接口中没有默认实现的任何成员依然必须显式实现。

在不同的泛型实例化中实现相同的接口

F#支持 在不同的泛型实例化中实现相同的接口,如下所示:

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"

另请参阅