【问题标题】:Creating an Action for unknown number and type of generic parameters为未知数量和类型的泛型参数创建操作
【发布时间】:2012-07-23 00:07:11
【问题描述】:

TL;DR

我正在寻找一种方法:

  • 创建一个Action 接受ParamArrayObject
  • 使用Expression 创建与给定数量/类型的泛型参数匹配的操作

详情

我正在编写一个将异步调用转换为阻塞调用的函数。我的函数需要一个要运行的任务、一个超时值以及一个可能因此发生的States(类似于事件)的集合。我们为多个泛型参数 IState(of T)、IState(of 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 动态创建我可以订阅的方法,但我之前对表达式树没有太多经验。有没有办法可以从我的 OnFinish 操作创建 Expression

我应该注意,处理状态的库不在我的控制范围内。底层代码检查参数的数量和可分配性(即 IsAssignableFrom)是否匹配,因此即使我将所有内容都作为IState 传递,它会找出底层类型真正是什么,并且然后告诉我我不能将我的Action 订阅到那个State

【问题讨论】:

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


    【解决方案1】:

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

    与代表

    可以在 VB.Net 中创建一个Delegate,它使用work-around 接受ParamArray。不知道这是否会奏效。也可以使用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
    

    我有通用方法“等待”一个结果状态,并在它发生时设置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
    

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

    【讨论】:

      猜你喜欢
      • 2013-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多