委托(F#)

委托将函数调用表示为对象。 在 F# 中,通常应使用函数值将函数表示为一类值;但是,委托在 .NET Framework 中使用,因此在与预期 API 进行互操作时需要这些委托。 创作旨在用于其他.NET框架语言的库时,也可以使用它们。

Syntax

type delegate-typename = delegate of type1 -> type2

Remarks

在前面的语法中, type1 表示参数类型或类型,并 type2 表示返回类型。 由其表示 type1 的参数类型会自动进行 curried。 这表明,如果对目标函数的参数进行 curried,则对于此类型,请使用元组窗体,并为元组窗体中已有的参数使用括号元组元组。 自动咖喱会删除一组括号,留下与目标方法匹配的元组参数。 请参阅代码示例,了解应在每种情况下使用的语法。

委托可以附加到 F# 函数值,以及静态或实例方法。 F# 函数值可以直接作为参数传递给委托构造函数。 对于静态方法,可以使用类的名称和方法构造委托。 对于实例方法,可以在一个参数中提供对象实例和方法。 在这两种情况下,都使用成员访问运算符 (.)。

Invoke委托类型上的方法调用封装的函数。 此外,可以通过引用 Invoke 方法名称而不带括号来传递委托作为函数值。

以下代码演示用于创建表示类中各种方法的委托的语法。 根据该方法是静态方法还是实例方法,以及它是否具有元组形式或 curried 形式的参数,声明和分配委托的语法略有不同。

type Test1() =
  static member add(a : int, b : int) =
     a + b
  static member add2 (a : int) (b : int) =
     a + b

  member x.Add(a : int, b : int) =
     a + b
  member x.Add2 (a : int) (b : int) =
     a + b


// Delegate1 works with tuple arguments.
type Delegate1 = delegate of (int * int) -> int
// Delegate2 works with curried arguments.
type Delegate2 = delegate of int * int -> int

let InvokeDelegate1 (dlg: Delegate1) (a: int) (b: int) =
   dlg.Invoke(a, b)
let InvokeDelegate2 (dlg: Delegate2) (a: int) (b: int) =
   dlg.Invoke(a, b)

// For static methods, use the class name, the dot operator, and the
// name of the static method.
let del1 = Delegate1(Test1.add)
let del2 = Delegate2(Test1.add2)

let testObject = Test1()

// For instance methods, use the instance value name, the dot operator, and the instance method name.
let del3 = Delegate1(testObject.Add)
let del4 = Delegate2(testObject.Add2)

for (a, b) in [ (100, 200); (10, 20) ] do
  printfn "%d + %d = %d" a b (InvokeDelegate1 del1 a b)
  printfn "%d + %d = %d" a b (InvokeDelegate2 del2 a b)
  printfn "%d + %d = %d" a b (InvokeDelegate1 del3 a b)
  printfn "%d + %d = %d" a b (InvokeDelegate2 del4 a b)

以下代码显示了处理委托的一些不同方法。

type Delegate1 = delegate of int * char -> string

let replicate n c = String.replicate n (string c)

// An F# function value constructed from an unapplied let-bound function
let function1 = replicate

// A delegate object constructed from an F# function value
let delObject = Delegate1(function1)

// An F# function value constructed from an unapplied .NET member
let functionValue = delObject.Invoke

List.map (fun c -> functionValue(5,c)) ['a'; 'b'; 'c']
|> List.iter (printfn "%s")

// Or if you want to get back the same curried signature
let replicate' n c =  delObject.Invoke(n,c)

// You can pass a lambda expression as an argument to a function expecting a compatible delegate type
// System.Array.ConvertAll takes an array and a converter delegate that transforms an element from
// one type to another according to a specified function.
let stringArray = System.Array.ConvertAll([|'a';'b'|], fun c -> replicate' 3 c)
printfn "%A" stringArray

前面的代码示例的输出如下所示。

aaaaa
bbbbb
ccccc
[|"aaa"; "bbb"|]

可以将名称添加到委托参数,如下所示:

// http://www.pinvoke.net/default.aspx/user32/WinEventDelegate.html
type WinEventDelegate = delegate of hWinEventHook:nativeint * eventType:uint32 * hWnd:nativeint * idObject:int * idChild:int * dwEventThread:uint32 * dwmsEventTime:uint32 -> unit

委托参数名称是可选的,将在方法中 Invoke 显示。 它们不需要与实现中的参数名称匹配。 它们只允许使用卷曲形式,但不允许使用卷曲形式。

type D1 = delegate of item1: int * item2: string -> unit
let a = D1(fun a b -> printf "%s" b)
a.Invoke(item2 = "a", item1 = 1) // Calling with named arguments

type D2 = delegate of int * item2: string -> unit // Omitting one name
let b = D2(fun a b -> printf "%s" b)
b.Invoke(1, item2 = "a")

前面的代码示例的输出如下所示。

aa

直接构造和框架元数据

为了保留基于反射的框架的参数名称和属性,F# 11 可以直接面向兼容的命名函数或方法,而不是生成的包装器。

例如,ASP.NET Core最小 API 参数绑定使用路由名称和绑定属性来填充处理程序参数:

open System
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Mvc
open Microsoft.Extensions.DependencyInjection

type ShipmentStore() =
    member _.Status trackingId =
        if trackingId = "SHP-1042" then "Dispatched" else "Unknown"

let status
    (trackingId: string)
    ([<FromServices>] store: ShipmentStore)
    ([<FromHeader(Name = "X-Customer")>] customer: string) =
    $"{customer}: {trackingId} is {store.Status trackingId}"

let handler = Func<string, ShipmentStore, string, string>(status)

通过以下方法 Method检查原始元数据:

let parameters = handler.Method.GetParameters()
let header =
    parameters[2].GetCustomAttributes(typeof<FromHeaderAttribute>, false)[0]
    :?> FromHeaderAttribute
assert (handler.Method.Name = "status")
assert (parameters[0].Name = "trackingId")
assert (parameters[1].IsDefined(typeof<FromServicesAttribute>, false))
assert (header.Name = "X-Customer")

有关完整的可运行应用程序,请参阅 示例项目。

哪些目标是直接的?

更倾向于 Func<...>(namedFunction) 在优化和未优化生成中直接构造。 显式转发 lambda 将包装器保留在未优化的内部版本中。 捕获、参数适应和强制内联扩展也需要包装器。

具有相同方法和接收器的直接委托相等。 对于可靠的事件取消订阅,请保留并重复使用订阅的委托。

Important

在直接委托构造之前验证实例接收器。 空接收器可以在调用前为非虚拟方法或NullReferenceException虚拟方法引发ArgumentException。

let receiver: System.Text.StringBuilder = null
let callback = System.Func<char, System.Text.StringBuilder>(receiver.Append)

当函数提供无时,包装器可以使用委托签名 Invoke 中的参数名称;它们不会保留原始方法的元数据。

另请参阅