F#:如何调用Expression。使用案例类型调用泛型类型中具有区别联合的方法?

时间:2019-06-08 00:05:53

标签: reflection .net-core f#

比方说,我有一个区分的联合类型AccountEvent和一个类Aggregate,它带有两种方法:

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

Event<'TEvent>只是一个具有通用类型的伪类。

我正在尝试创建一个Expression来表示对Apply1Apply2的调用,以支持参数类型Discriminated Union case类型。 这允许:

  • AccountEvent.AccountCreated的类型Apply1
  • Event<AccountEvent.AccountCreated>的类型Apply2

我想实现这一目标,而无需更改Apply1Apply2的签名以及已定义联合的定义。

代码

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.AccountCreatedEvent<AccountEvent>的通用名称。

1 个答案:

答案 0 :(得分:1)

很难说这是否回答了您的问题。

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

之后,这样的match语句可能会简化所有操作。老实说,要精确地了解您在此处所做的事情有点复杂,但是使用函数而不是方法以及使用match语句似乎可以实现相同的目标。理想情况下,您可能应该完全拼写DU中的类型,而不要使用泛型,这样您将获得编译时检查而不是运行时错误,并且可以肯定地知道编译器完全覆盖了您的代码。