【发布时间】:2010-12-23 05:36:28
【问题描述】:
有时进行方法调用、完成参数并将其转换为 MethodInvoker 很有用,该方法将使用这些参数调用指定的函数,而无需当时指定参数。在其他时候,做一些类似的事情很有用,但要保留一些参数。这种类型的动作称为“Currying”。在 VB 中执行此操作的最佳模式是什么?
在 VB 2010 中可以使用 lambda 表达式,但 lambda 表达式与 edit-and-continue 不兼容,并且它们创建的闭包可能具有意外的引用行为。另一种方法是定义一些通用方法,如下所示:
Public Module CurryMagic
Delegate Sub Action(Of T1, T2)(ByVal P1 As T1, ByVal P2 As T2)
Delegate Sub Action(Of T1, T2, T3)(ByVal P1 As T1, ByVal P2 As T2, ByVal P3 As T3)
Class CurriedAction0(Of FixedType1, FixedType2)
Dim _theAction As Action(Of FixedType1, FixedType2)
Dim _FixedVal1 As FixedType1, _FixedVal2 As FixedType2
Sub Exec()
_theAction(_FixedVal1, _FixedVal2)
End Sub
Sub New(ByVal theAction As Action(Of FixedType1, FixedType2), _
ByVal FixedVal1 As FixedType1, ByVal FixedVal2 As FixedType2)
_theAction = theAction
_FixedVal1 = FixedVal1
_FixedVal2 = FixedVal2
End Sub
End Class
Class CurriedAction1(Of ArgType1, FixedType1, FixedType2)
Dim _theAction As Action(Of ArgType1, FixedType1, FixedType2)
Dim _FixedVal1 As FixedType1, _FixedVal2 As FixedType2
Sub Exec(ByVal ArgVal1 As ArgType1)
_theAction(ArgVal1, _FixedVal1, _FixedVal2)
End Sub
Sub New(ByVal theAction As Action(Of ArgType1, FixedType1, FixedType2), _
ByVal FixedVal1 As FixedType1, ByVal FixedVal2 As FixedType2)
_theAction = theAction
_FixedVal1 = FixedVal1
_FixedVal2 = FixedVal2
End Sub
End Class
Class ActionOf(Of ArgType1)
Shared Function Create(Of FixedType1, FixedType2)(ByVal theSub As Action(Of ArgType1, FixedType1, FixedType2), ByVal FixedVal1 As FixedType1, ByVal FixedVal2 As FixedType2) As Action(Of ArgType1)
Return AddressOf New CurriedAction1(Of ArgType1, FixedType1, FixedType2)(theSub, FixedVal1, FixedVal2).Exec
End Function
End Class
Function NewInvoker(Of FixedType1, FixedType2)(ByVal theSub As Action(Of FixedType1, FixedType2), ByVal FixedVal1 As FixedType1, ByVal FixedVal2 As FixedType2) As MethodInvoker
Return AddressOf New CurriedAction0(Of FixedType1, FixedType2)(theSub, FixedVal1, FixedVal2).Exec
End Function
End Module
如果我想创建一个 MethodInvoker 来执行 Foo(5, "Hello"),我可以创建一个使用
MyInvoker = NewInvoker(AddressOf Foo, 5, "Hello")
如果我想将 MyAction(X) 变成 Boz(X, "George", 9),其中 X 是 Double,我可以使用
MyAction = ActionOf(Of Double).Create(AddressOf Boz, "George", 9)
除了需要大量样板代码来容纳不同数量的固定和非固定参数之外,一切都非常流畅,并且委托创建语法中没有任何内在的东西可以明确哪些参数是固定的,哪些是固定的是非固定的。有没有办法改进模式?
附录: 如果委托是从结构成员函数创建的,那么机制是什么?似乎委托获得了自己的结构副本,但我不知道该副本是装箱还是未装箱。如果没有装箱,用结构替换 CurryAction0 和 CurryAction1 将避免在创建委托时将 CurryAction0 或 CurryAction1 分配为单独的堆对象。但是,如果要装箱,则使用结构会增加将结构复制到装箱实例的开销,而不会保存任何内容。
【问题讨论】:
-
我不确定您想要什么,但我先打个招呼:您可以在不指定参数的情况下创建委托,然后通过 Invoke 方法将它们作为 Object-Array 传递。
-
'这种类型的动作被称为“Currying”'——实际上它是部分应用。 “柯里化”是指某些语言构造函数以使其易于部分应用的方式。 en.wikipedia.org/wiki/…
-
@Tim Robinson: 嗯...根据那个描述,柯里化将是创建执行部分应用程序的方法的过程,与执行应用程序本身的行为不同?无论如何,我一直在寻找一种很好的方法来做到这一点。我的方法需要大量样板代码才能工作,这很烦人,但避免了闭包带来的麻烦。顺便说一句,结构上的代表是装箱还是拆箱?如果取消装箱,我的模式可以通过使用结构而不是类来改进,但如果装箱,那将是一种浪费。
-
@Bobby:在调用之前可以忽略参数类型,但我更喜欢在实际情况下允许编译时类型检查的方式来做事。这不仅可以更早地发现问题,而且还可以扩大在运行时无法完成的类型转换。
-
@MikeRosenblum:如果一个结构实现了一个接口,则在该接口类型的变量上使用的接口方法将操作并可以修改装箱实例。这种行为在 List
.GetEnumerator() 返回的结构中最为明显,它实现了 IEnumerator .
标签: .net vb.net delegates partial-application