デリゲートは、関数呼び出しをオブジェクトとして表します。 F# では、通常、関数の値を使用して、関数をファースト クラスの値として表す必要があります。ただし、デリゲートは .NET Framework で使用されるため、必要な API と相互運用する場合に必要になります。 また、他の .NET Framework 言語から使用できるように設計されたライブラリを作成するときにも使用できます。
構文
type delegate-typename = delegate of type1 -> type2
注釈
前の構文では、 type1 は引数の型または型を表し、 type2 は戻り値の型を表します。
type1によって表される引数の型は自動的にカリー化されます。 これは、この型では、ターゲット関数の引数がカリー化されている場合はタプル フォームを使用し、既にタプル形式の引数にはかっこで分けたタプルを使用することを示唆しています。 自動カリー処理では、一連のかっこが削除され、ターゲット メソッドに一致するタプル引数が残ります。 各ケースで使用する必要がある構文については、コード例を参照してください。
デリゲートは、F# 関数の値、および静的メソッドまたはインスタンス メソッドにアタッチできます。 F# 関数の値は、デリゲート コンストラクターに引数として直接渡すことができます。 静的メソッドの場合は、クラスとメソッドの名前を使用してデリゲートを構築します。 インスタンス メソッドの場合は、オブジェクト インスタンスとメソッドを 1 つの引数に指定します。 どちらの場合も、メンバー アクセス演算子 (.) が使用されます。
デリゲート型の Invoke メソッドは、カプセル化された関数を呼び出します。 また、かっこなしで Invoke メソッド名を参照することで、デリゲートを関数値として渡すことができます。
次のコードは、クラス内のさまざまなメソッドを表すデリゲートを作成するための構文を示しています。 メソッドが静的メソッドかインスタンス メソッドか、タプル 形式またはカリー化された形式の引数があるかどうかによって、デリゲートを宣言して割り当てる構文は少し異なります。
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 では、生成されたラッパーではなく、互換性のある名前付き関数またはメソッドを直接ターゲットにすることができます。
たとえば、最小 API パラメーター バインド ASP.NET Coreルート名とバインド属性を使用してハンドラー引数を設定します。
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) を優先します。 明示的な転送ラムダは、最適化されていないビルドでそのラッパーを保持します。 キャプチャ、引数の適応、必須のインライン拡張にはラッパーも必要な場合があります。
同じメソッドと受信側を持つ直接デリゲートが等しいと比較されます。 信頼できるイベントのサブスクリプションを解除するには、サブスクライブされたデリゲートを保持して再利用します。
Important
直接デリゲート構築の前にインスタンス レシーバーを検証します。 null レシーバーは、非仮想メソッドの ArgumentException をスローするか、呼び出し前に仮想メソッドの NullReferenceException をスローできます。
let receiver: System.Text.StringBuilder = null
let callback = System.Func<char, System.Text.StringBuilder>(receiver.Append)
ラッパーは、関数が何も提供しない場合、デリゲートの Invoke シグネチャのパラメーター名を使用できます。元のメソッドのメタデータは保持されません。
こちらも参照ください
.NET