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



假设,我有一个可区分的联合类型AccountEvent和一个带有两种方法的类Aggregate

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

Event<'TEvent>只是一个虚拟类,以便拥有泛型类型。

我正在尝试创建一个表示对Apply1调用的Expression,并支持参数类型"区分联合案例类型"Apply2。 这允许:

  • Apply1AccountEvent.AccountCreated类型
  • Apply2Event<AccountEvent.AccountCreated>类型

我想在不改变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:UserseperretDesktopConsoleApp1ConsoleApp1Program.fs:40
at Program.main(String[] argv) in C:UserseperretDesktopConsoleApp1ConsoleApp1Program.fs:61

我想知道如何调整表达式的创建以接受Event<AccountEvent.AccountCreated>

我在想,也许需要一个中间层来有一个从AccountEvent.AccountCreated到其基类AccountEvent的转换层(这就是区分联合的编译方式),或者更准确地说,考虑泛型,从Event<AccountEvent.AccountCreatedEvent<AccountEvent>

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

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 中完全拼写出类型,而不是使用泛型,以便获得编译时检查而不是运行时错误,并且可以确定编译器完全覆盖了您的代码。

最新更新