类型扩展

类型扩展(也称为 扩充)是一系列功能,可用于向以前定义的对象类型添加新成员。 这三个功能包括:

  • 内部类型扩展
  • 可选类型扩展
  • 扩展方法

每个方案可用于不同的方案,并具有不同的权衡。

语法

// 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.Summember 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此代码就会使其看起来好像SumIEnumerable<T>已打开或处于打开状态。

若要使扩展可用于 VB.NET 代码,程序集级别需要额外的 ExtensionAttribute 扩展:

module AssemblyInfo
open System.Runtime.CompilerServices
[<assembly:Extension>]
do ()

其他备注

类型扩展还具有以下属性:

  • 可以访问的任何类型都可以扩展。
  • 内部和可选类型扩展可以定义 任何 成员类型,而不仅仅是方法。 因此,还可以使用扩展属性,例如。
  • self-identifier 语法中的标记表示要调用的类型实例,就像普通成员一样。
  • 扩展成员可以是静态成员或实例成员。
  • 类型扩展上的类型变量必须与声明类型的约束匹配。

类型扩展还存在以下限制:

  • 类型扩展不支持虚拟或抽象方法。
  • 类型扩展不支持替代方法作为扩充。
  • 类型扩展不支持 静态解析的类型参数
  • 可选类型扩展不支持作为扩充的构造函数。
  • 类型扩展不能在 类型缩写上定义。
  • 类型扩展无效 byref<'T> (尽管可以声明它们)。
  • 类型扩展对属性无效(尽管可以声明它们)。
  • 可以定义重载其他同名方法的扩展,但如果调用不明确,F# 编译器会优先使用非扩展方法。

最后,如果一种类型存在多个内部类型扩展,则所有成员都必须是唯一的。 对于可选类型扩展,同一类型的不同类型扩展中的成员可以具有相同的名称。 仅当客户端代码打开定义相同成员名称的两个不同的范围时,才会发生歧义错误。

另请参阅