为未知数量和通用参数类型创建操作

时间:2012-07-24 16:02:29

标签: .net vb.net generics .net-3.5 expression-trees

TL; DR

我正在寻找一种方法:

  • 创建一个Action ParamArray Object
  • 使用Expression创建与给定数量/类型的通用参数匹配的Action

详情

我正在编写一个将异步调用转换为阻塞调用的函数。我的函数需要运行一个任务,一个超时值,以及一个可能因此而发生的States(类似事件)的集合。我们为多个通用参数IState(T),IState(T1,T2)等定义了States ...

Function MakeBlocking(task As Action,
                      resultingStates As IEnumberable(of IState),
                      Optional ByVal millisecondsTimeout As Integer = -1) 
                      As Boolean

  Dim are As New AutoResetEvent(False)
  Dim onFinish As New Action(Sub() are.Set())

  For Each state In resultingStates
    state.Subscribe(onFinish)
  Next

  task.Invoke()

  Dim result = are.WaitOne(millisecondsTimeout)

  For Each state In resultingStates
    state.Unsubscribe(onFinish)
  Next

  Return result
End Function

我希望能够接受具有任何数量和类型参数的IState集合。由于IState(Of T)继承自IState,而IState(Of T1, T2)继承自IState(Of T),我认为IEnumerable(Of IState)会有效。问题是Action我订阅了它们并没有匹配的参数。

Public Interface IState(Of T1, T2)
  Inherits IState(Of T1)

  Shadows Function Subscribe(ByVal action As Action(Of T1, T2)) As IStateSubscription
  Shadows Function Unsubscribe(ByVal action As Action(Of T1, T2)) As Boolean

End Interface

正如您所看到的,如果我的结果状态为IState(Of String, Boolean)IState(Of Integer)IState(Of String),则我需要为每个状态单独Action。我真的希望能够做到这样的事情:

Dim onFinish As New Action(Sub(ParamArray stuff As Object())(Sub() are.Set())

但我似乎无法找到验证语法(如果可能的话)。看起来唯一的另一个选择是使用Expression.Lambda来动态创建我可以订阅的方法,但我以前没有很多表达树的经验。我可以通过Expression动作创建OnFinish吗?

我应该注意,处理国家的图书馆不受我的控制。底层代码检查参数在数量和可分配性方面是否匹配(即IsAssignableFrom),所以即使我将所有内容都传递为IState,它也会找出基础类型真的是,然后告诉我,我无法订阅我的Action State

1 个答案:

答案 0 :(得分:0)

这就是我想出来的......

与代表

可以在VB.Net中创建DelegateParamArray使用work-around。不确定这是否会奏效。也可以使用Expression动态创建委托:

Dim are As New AutoResetEvent(False)

Dim setMethod = are.GetType.GetMethod("Set")
Dim callSet = Expression.Call(Expression.Constant(are), setMethod)

For Each state In resultingStates
  Dim args = state.GetType.GetGenericArguments()
  Dim params = Enumerable.Range(1, args.Count).Select(
                 Function(x) Expression.Parameter(args(x - 1), "var" & x))
  Dim onFinish = Expression.Lambda(callSet, params.ToArray).Compile()

  state.Subscribe(onFinish) 'Doesn't compile since OnFinish isn't an Action
Next

但是,这些选项都不起作用,因为我需要一个Action。看起来我可以make an Action from a Delegate,但我需要知道Action的泛型类型,所以我可以,在我的情况下直到运行时才能使用的东西......(我相信你不能没有静态知道类型的演员。)

我的解决方案

我最终分开了一些问题。以前,我会这样称呼它:

Public Function GetThing() As Boolean 'Returns success vs timeout
  Return MakeBlocking(Sub() SendAsyncThingRequest(), 
                      {ResultingState1, ResultingState2}, 
                      timeOutVal)
End Sub

我反而将其分解出来,以便来电者承担更多责任:

Public Function GetThing() As Boolean
  Dim are As New AutoResetEvent(False)

  WaitFor(ResultingState1, are)
  WaitFor(ResultingState2, are)

  SendAsyncThingRequest()

  Return are.WaitOne(timeoutVal)
End Function

我有“WaitFor”结果状态的通用方法,并在发生时设置AutoResetEvent

Protected Sub WaitFor(Of T)(ByVal waitUpon As IState(Of T), 
                            ByVal are As AutoResetEvent)

  Dim action As New Action(Of T)(
  Sub(x)
    are.Set()
    waitUpon.Unsubscribe(action)
  End Sub)

  waitUpon.Subscribe(action)
End Sub

这最终成为我特定场景中最简单/最干净的解决方案。