签名
签名文件包含有关一组 F# 程序元素(如类型、命名空间和模块)的公共签名的信息。 它可用于指定这些程序元素的可访问性。
注解
对于每个 F# 代码文件,均可具有 签名文件,该文件的名称与代码文件相同,但其扩展名为 .fsi 而非 .fs。 如果你直接使用命令行,签名文件还可添加到编译命令行。 为了区分代码文件和签名文件,代码文件有时称为 实现文件。 在项目中,签名文件应位于关联的代码文件之前。
签名文件描述相应的实现文件中的命名空间、模块、类型和成员。 你使用签名文件中的信息来指定相应实现文件中的哪部分代码可从实现文件外部的代码进行访问、哪部分代码属于实现文件内部。 包含在签名文件中的命名空间、模块和类型必须是包含在实现文件中的命名空间、模块和类型的子集。 除了本主题后面记录的一些例外之外,签名文件中未列出的那些语言元素被视为实现文件专用元素。 如果项目或命令行中未找到签名文件,则使用默认可访问性。
有关默认可访问性的详细信息,请参阅访问控制。
在签名文件中,你不会重复定义类型以及实现每个方法或函数。 相反,可以对每个方法和函数使用签名,它将作为由模块或命名空间片段实现的功能的完整规范。 类型签名的语法与在接口和抽象类的抽象方法声明中使用的语法相同,并且当它正确显示编译的输入时还由 IntelliSense 和 F# interpreter fsi.exe 显示。
如果类型签名中没有足够的信息来指示类型是否密封或它是否为接口类型,那么你必须添加一个特性,指示编译器类型的性质。 下表中描述了用于此目的的特性。
Attribute | 说明 |
---|---|
[<Sealed>] |
适用于没有抽象成员或不应扩展的类型。 |
[<Interface>] |
适用于接口类型。 |
如果实现文件中的签名和声明的特性不一致,则编译器将产生错误。
使用关键字 val
来创建用于值或函数值的签名。 关键字 type
引入类型签名。
你可使用 --sig
编译器选项生成签名文件。 通常情况下,不用手动编写 .fsi 文件。 相反,可通过使用编译器生成 .fsi 文件,将它们添加到项目中(如果存在),并通过删除不希望被访问的方法和函数对其进行编辑。
类型签名存在几个规则:
实现文件中的类型缩写不得与签名文件中没有缩写的类型匹配。
记录和可辨识的联合必须要么公开其所有字段和构造函数,要么不公开其任何字段和构造函数,并且签名中的顺序必须匹配实现文件中的顺序。 类可以公开签名中部分或全部字段和方法或者不公开任何字段和方法。
具有构造函数的类和结构必须公开其基类的声明(
inherits
声明)。 此外,具有构造函数的类和结构必须公开其所有的抽象方法和接口声明。接口类型必须显示其所有方法和接口。
值签名的规则如下所示:
签名中的可访问性修饰符(
public
、internal
等)以及inline
和mutable
修饰符必须与实现中的修饰符匹配。泛型类型参数的数目(不管是隐式推断还是显式声明)必须匹配,并且泛型类型参数中的类型和类型约束必须匹配。
如果使用了
Literal
特性,则它必须同时显示在签名和实现中,且必须为两者使用相同的文字值。签名和实现的参数模式(也称为 arity)必须一致。
如果签名文件的参数名称与相应的实现文件不同,则改为使用签名文件中的名称,这可能会导致调试或分析时出现问题。 如果希望收到有关此类不匹配的通知,请在项目文件中启用警告 3218,或在调用编译器时启用它(请参阅编译器选项下的
--warnon
)。
下面的代码示例显示具有命名空间、模块、函数值、类型签名以及相应特性的签名文件示例。 它还显示相应的实现文件。
// Module1.fsi
namespace Library1
module Module1 =
val function1 : int -> int
type Type1 =
new : unit -> Type1
member method1 : unit -> unit
member method2 : unit -> unit
[<Sealed>]
type Type2 =
new : unit -> Type2
member method1 : unit -> unit
member method2 : unit -> unit
[<Interface>]
type InterfaceType1 =
abstract member method1 : int -> int
abstract member method2 : string -> unit
下面的代码演示实现文件。
namespace Library1
module Module1 =
let function1 x = x + 1
type Type1() =
member type1.method1() =
printfn "type1.method1"
member type1.method2() =
printfn "type1.method2"
[<Sealed>]
type Type2() =
member type2.method1() =
printfn "type2.method1"
member type2.method2() =
printfn "type2.method2"
[<Interface>]
type InterfaceType1 =
abstract member method1 : int -> int
abstract member method2 : string -> unit