接口 (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该方法具有具有名称format的类型string的单一参数。

可通过两种方法实现接口:使用对象表达式和类型。 在任一情况下,类型或对象表达式都为接口的抽象方法提供方法主体。 实现特定于实现接口的每个类型。 因此,不同类型的接口方法可能彼此不同。

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

.NET 编码样式是开始所有具有大写 I接口的接口。

可以通过两种方式指定多个参数:F#样式和 。NET 样式。 这两者都会为 .NET 使用者编译相同的方法,但 F#-style 将强制 F# 调用方使用 F#样式参数应用程序和 。NET 样式将强制 F# 调用方使用 tupled 参数应用程序。

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

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

使用类类型实现接口

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

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"

另请参阅