假设,我有一个可区分的联合类型AccountEvent
和一个带有两种方法的类Aggregate
:
Apply1(event : AccountEvent)
Apply2(event : Event<AccountEvent>)
Event<'TEvent>
只是一个虚拟类,以便拥有泛型类型。
我正在尝试创建一个表示对Apply1
调用的Expression
,并支持参数类型"区分联合案例类型"Apply2
。 这允许:
Apply1
的AccountEvent.AccountCreated
类型Apply2
的Event<AccountEvent.AccountCreated>
类型
我想在不改变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:UserseperretDesktopConsoleApp1ConsoleApp1Program.fs:40
at Program.main(String[] argv) in C:UserseperretDesktopConsoleApp1ConsoleApp1Program.fs:61
我想知道如何调整表达式的创建以接受Event<AccountEvent.AccountCreated>
?
我在想,也许需要一个中间层来有一个从AccountEvent.AccountCreated
到其基类AccountEvent
的转换层(这就是区分联合的编译方式),或者更准确地说,考虑泛型,从Event<AccountEvent.AccountCreated
到Event<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 中完全拼写出类型,而不是使用泛型,以便获得编译时检查而不是运行时错误,并且可以确定编译器完全覆盖了您的代码。