대리자(F#)

대리자는 함수 호출을 개체로 나타냅니다. F#에서는 일반적으로 함수 값을 사용하여 함수를 일류 값으로 나타내야 합니다. 그러나 대리자는 .NET Framework에서 사용되므로 필요한 API와 상호 운용할 때 필요합니다. 다른 .NET Framework 언어에서 사용하도록 설계된 라이브러리를 작성할 때도 사용할 수 있습니다.

문법

type delegate-typename = delegate of type1 -> type2

비고

이전 구문 type1 에서 인수 형식 또는 형식을 나타내고 type2 반환 형식을 나타냅니다. 표시되는 type1 인수 형식은 자동으로 커서가 지정됩니다. 이는 대상 함수의 인수가 튜플 형식인 경우 튜플 폼을 사용하고 튜플 형식에 이미 있는 인수에 대해 괄호가 지정된 튜플을 사용한다는 것을 시사합니다. 자동 커리는 괄호 집합을 제거하여 대상 메서드와 일치하는 튜플 인수를 남깁니다. 각 사례에서 사용해야 하는 구문에 대한 코드 예제를 참조하세요.

대리자는 F# 함수 값 및 정적 또는 인스턴스 메서드에 연결할 수 있습니다. F# 함수 값을 대리자 생성자에 대한 인수로 직접 전달할 수 있습니다. 정적 메서드의 경우 클래스 이름과 메서드를 사용하여 대리자를 생성합니다. 인스턴스 메서드의 경우 개체 인스턴스와 메서드를 하나의 인수로 제공합니다. 두 경우 모두 멤버 액세스 연산자(.)가 사용됩니다.

대리자 형식의 메서드는 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은 생성된 래퍼 대신 호환되는 명명된 함수 또는 메서드를 직접 대상으로 지정할 수 있습니다.

예를 들어 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) 합니다. 명시적 전달 람다는 해당 래퍼를 최적이 아닌 빌드에 유지합니다. 캡처, 인수 적응 및 필수 인라인 확장에도 래퍼가 필요할 수 있습니다.

메서드와 수신기가 같은 직접 대리자가 같은지 비교합니다. 신뢰할 수 있는 이벤트 구독 취소의 경우 구독된 대리자를 보존하고 다시 사용합니다.

Important

직접 대리자를 생성하기 전에 인스턴스 수신기의 유효성을 검사합니다. null 수신기는 호출 전에 가상이 아닌 메서드 또는 NullReferenceException 가상 메서드에 대해 throw ArgumentException 할 수 있습니다.

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

래퍼는 함수가 아무 것도 제공하지 않을 때 대리자 서명의 Invoke 매개 변수 이름을 사용할 수 있으며 원래 메서드의 메타데이터를 유지하지 않습니다.

참고하십시오