类型扩展(也称为 扩充)是一系列功能,可用于向以前定义的对象类型添加新成员。 这三个功能包括:
- 内部类型扩展
- 可选类型扩展
- 扩展方法
每个方案可用于不同的方案,并具有不同的权衡。
语法
// Intrinsic and optional extensions
type typename with
member self-identifier.member-name =
body
...
// Extension methods
open System.Runtime.CompilerServices
[<Extension>]
type Extensions() =
[<Extension>]
static member extension-name (ty: typename, [args]) =
body
...
内部类型扩展
内部类型扩展是扩展用户定义类型的类型扩展。
内部类型扩展必须在同一文件中 定义,并且 必须在同一命名空间或模块中定义它们正在扩展的类型。 任何其他定义都将导致它们成为 可选的类型扩展。
内部类型扩展有时是将功能与类型声明分开的更简洁的方法。 以下示例演示如何定义内部类型扩展:
namespace Example
type Variant =
| Num of int
| Str of string
module Variant =
let print v =
match v with
| Num n -> printf "Num %d" n
| Str s -> printf "Str %s" s
// Add a member to Variant as an extension
type Variant with
member x.Print() = Variant.print x
使用类型扩展可以分隔以下各项:
- 类型的声明
Variant
- 根据类的“形状”打印
Variant
类的功能 - 使用对象样式
.
-表示法访问打印功能的方法
这是将所有内容定义为成员的 Variant
替代方法。 虽然这不是一种本质上更好的方法,但在某些情况下,它可以更清晰地表示功能。
内部类型扩展被编译为它们增强的类型的成员,并在通过反射检查类型时显示在类型上。
可选类型扩展
可选类型扩展是在要扩展的类型的原始模块、命名空间或程序集外部显示的扩展。
可选类型扩展可用于扩展尚未自行定义的类型。 例如:
module Extensions
type IEnumerable<'T> with
/// Repeat each element of the sequence n times
member xs.RepeatElements(n: int) =
seq {
for x in xs do
for _ in 1 .. n -> x
}
现在,只要模块在正在使用的作用域中打开,就可以访问RepeatElements
它是否属于该模块的成员IEnumerable<T>。Extensions
通过反射检查时,可选扩展不会显示在扩展类型上。 可选扩展必须位于模块中,并且仅当包含扩展的模块处于打开状态或处于范围内时,它们才在范围内。
可选扩展成员编译为作为第一个参数隐式传递对象实例的静态成员。 但是,根据实例成员或静态成员的声明方式,它们就像是实例成员或静态成员一样。
C# 或 Visual Basic 使用者也看不到可选扩展成员。 它们只能在其他 F# 代码中使用。
固有和可选类型扩展的泛型限制
可以在受类型变量约束的泛型类型上声明类型扩展。 要求是扩展声明的约束与声明类型的约束匹配。
但是,即使在声明的类型和类型扩展之间匹配约束,也可以由扩展成员的主体推断约束,该主体对类型参数施加与声明类型不同的要求。 例如:
open System.Collections.Generic
// NOT POSSIBLE AND FAILS TO COMPILE!
//
// The member 'Sum' has a different requirement on 'T than the type IEnumerable<'T>
type IEnumerable<'T> with
member this.Sum() = Seq.sum this
无法获取此代码以使用可选类型扩展:
与类型扩展定义的约束 不同,成员对 > 的约束也不同。 - 将类型扩展修改为具有相同的约束,不再
Sum
与定义的约束IEnumerable<'T>
匹配。 - 更改为
member this.Sum
member inline this.Sum
将提供类型约束不匹配的错误。
需要的是“在空间中浮动”的静态方法,可以像扩展类型一样显示。 这是需要扩展方法的地方。
扩展方法
最后,扩展方法(有时称为“C# 样式扩展成员”)可以在 F# 中声明为类上的静态成员方法。
当你希望定义将约束类型变量的泛型类型的扩展时,扩展方法非常有用。 例如:
namespace Extensions
open System.Collections.Generic
open System.Runtime.CompilerServices
[<Extension>]
type IEnumerableExtensions =
[<Extension>]
static member inline Sum(xs: IEnumerable<'T>) = Seq.sum xs
使用时,只要已打开或处于作用域内,Extensions
此代码就会使其看起来好像Sum
IEnumerable<T>已打开或处于打开状态。
若要使扩展可用于 VB.NET 代码,程序集级别需要额外的 ExtensionAttribute
扩展:
module AssemblyInfo
open System.Runtime.CompilerServices
[<assembly:Extension>]
do ()
其他备注
类型扩展还具有以下属性:
- 可以访问的任何类型都可以扩展。
- 内部和可选类型扩展可以定义 任何 成员类型,而不仅仅是方法。 因此,还可以使用扩展属性,例如。
-
self-identifier
语法中的标记表示要调用的类型实例,就像普通成员一样。 - 扩展成员可以是静态成员或实例成员。
- 类型扩展上的类型变量必须与声明类型的约束匹配。
类型扩展还存在以下限制:
- 类型扩展不支持虚拟或抽象方法。
- 类型扩展不支持替代方法作为扩充。
- 类型扩展不支持 静态解析的类型参数。
- 可选类型扩展不支持作为扩充的构造函数。
- 类型扩展不能在 类型缩写上定义。
- 类型扩展无效
byref<'T>
(尽管可以声明它们)。 - 类型扩展对属性无效(尽管可以声明它们)。
- 可以定义重载其他同名方法的扩展,但如果调用不明确,F# 编译器会优先使用非扩展方法。
最后,如果一种类型存在多个内部类型扩展,则所有成员都必须是唯一的。 对于可选类型扩展,同一类型的不同类型扩展中的成员可以具有相同的名称。 仅当客户端代码打开定义相同成员名称的两个不同的范围时,才会发生歧义错误。