比方说,我有一个区分的联合类型AccountEvent
和一个类Aggregate
,它带有两种方法:
Apply1(event : AccountEvent)
Apply2(event : Event<AccountEvent>)
Event<'TEvent>
只是一个具有通用类型的伪类。
我正在尝试创建一个Expression
来表示对Apply1
和Apply2
的调用,以支持参数类型Discriminated Union case类型。
这允许:
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>
的通用名称。
答案 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中的类型,而不要使用泛型,这样您将获得编译时检查而不是运行时错误,并且可以肯定地知道编译器完全覆盖了您的代码。