F #: Как вызвать Expression.Call для метода с дискриминированным объединением в универсальном типе, использующем тип регистра? - PullRequest
2 голосов
/ 08 июня 2019

Допустим, у меня есть различенный тип объединения AccountEvent и класс Aggregate, который имеет два метода:

  • Apply1(event : AccountEvent)
  • Apply2(event : Event<AccountEvent>)

Event<'TEvent> - просто фиктивный класс ради общего типа.

Я пытаюсь создать Expression, представляющий вызов Apply1 и Apply2, поддерживающий для типа параметра тип случая Дискриминационное объединение.Это позволяет:

  • AccountEvent.AccountCreated тип для Apply1
  • Event<AccountEvent.AccountCreated> тип для Apply2

Я хочу добиться этого без без изменения сигнатуры Apply1, Apply2 и определения дискриминированного объединения.

Код

type AccountCreation = {
    Owner: string
    AccountId: Guid
    CreatedAt: DateTimeOffset
    StartingBalance: decimal
}

type Transaction = {
    To: Guid
    From: Guid
    Description: string
    Time: DateTimeOffset
    Amount: decimal
}

type AccountEvent =
    | AccountCreated of AccountCreation
    | AccountCredited of Transaction
    | AccountDebited of Transaction

type Event<'TEvent>(event : 'TEvent)=
    member val Event = event with get

type Aggregate()=
    member this.Apply1(event : AccountEvent)=
        ()

    member this.Apply2(event : Event<AccountEvent>)=
        ()

let createExpression (aggregateType: Type)(eventType: Type)(method: MethodInfo) =
    let instance = Expression.Parameter(aggregateType, "a")
    let eventParameter = Expression.Parameter(eventType, "e")
    let body = Expression.Call(instance, method, eventParameter)
    ()

[<EntryPoint>]
let main argv =

    let accountCreated = AccountEvent.AccountCreated({
        Owner = "Khalid Abuhakmeh"
        AccountId = Guid.NewGuid()
        StartingBalance = 1000m
        CreatedAt = DateTimeOffset.UtcNow
    })
    let accountCreatedType = accountCreated.GetType()

    let method1 = typeof<Aggregate>.GetMethods().Single(fun x -> x.Name = "Apply1")
    createExpression typeof<Aggregate> typeof<AccountEvent> method1
    createExpression typeof<Aggregate> accountCreatedType method1

    let method2 = typeof<Aggregate>.GetMethods().Single(fun x -> x.Name = "Apply2")
    let eventAccountCreatedType = typedefof<Event<_>>.MakeGenericType(accountCreatedType)
    createExpression typeof<Aggregate> typeof<Event<AccountEvent>> method2
    createExpression typeof<Aggregate> eventAccountCreatedType method2

    0

С моим текущим решением это не работает длясгенерировать выражение для Apply2:

System.ArgumentException: Expression of type 'Program+Event`1[Program+AccountEvent+AccountCreated]' cannot be used for parameter of type 'Program+Event`1[Program+AccountEvent]' of method 'Void Apply2(Event`1)'
Parameter name: arg0
  at at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index)
  at at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression arg0)
  at at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
  at at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression[] arguments)
  at Program.doingStuff(Type aggregateType, Type eventType, MethodInfo method) in C:\Users\eperret\Desktop\ConsoleApp1\ConsoleApp1\Program.fs:40
  at Program.main(String[] argv) in C:\Users\eperret\Desktop\ConsoleApp1\ConsoleApp1\Program.fs:61

Мне интересно, как я могу настроить создание своего выражения так, чтобы оно принимало Event<AccountEvent.AccountCreated>?

Я думаю, что, возможно, существуетнеобходимо иметь промежуточный уровень, чтобы иметь уровень преобразования из AccountEvent.AccountCreated в его базовый класс AccountEvent (так компилируются разграниченные объединения), или, точнее, с учетом универсального уровня, от Event<AccountEvent.AccountCreated до Event<AccountEvent>.

1 Ответ

1 голос
/ 08 июня 2019

трудно сказать, отвечает ли это на ваш вопрос.

open System
open System
type AccountCreation = {
    Owner: string
    AccountId: Guid
    CreatedAt: DateTimeOffset
    StartingBalance: decimal
}

type Transaction = {
    To: Guid
    From: Guid
    Description: string
    Time: DateTimeOffset
    Amount: decimal
}

type AccountEvent =
    | AccountCreated of AccountCreation
    | AccountCredited of Transaction
    | AccountDebited of Transaction
type CheckinEvent =
    | CheckedIn
    | CheckedOut
type Event<'T> = AccountEvent of AccountEvent | OtherEvent of 'T
let ev : Event<CheckinEvent> = AccountEvent (AccountCreated {
      Owner= "string"
      AccountId= Guid.NewGuid()
      CreatedAt=  DateTimeOffset()
      StartingBalance=0m
    })
let ev2 : Event<CheckinEvent> = OtherEvent CheckedOut
let f ev =
  match ev with
      | AccountEvent e -> Some e
      | OtherEvent (CheckedOut) -> None 
      | OtherEvent (CheckedIn) -> None 

let x = f ev
let y = f ev2

Впоследствии подобное выражение может упростить все это.Честно говоря, мне немного сложно следить за тем, что именно вы там делаете, но использование функции вместо метода и использование оператора сравнения, похоже, для достижения той же цели.В идеале вам, вероятно, следует полностью прописать типы в DU вместо использования универсального, так что вы получите проверки времени компиляции вместо ошибок времени выполнения и сможете точно знать, что ваш код полностью покрыт компилятором.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...