tags:

views:

133

answers:

3

I would like to have a function that can "wrap" any other function call. In this particular case, it would allow me to write some more generic transaction handling around some specific operations.

I can write this for any particular number of arguments, e.g. for one argument:

Public Shared Sub WrapFunc(Of T)(ByVal f As Action(Of T), ByVal arg As T)
    ' Test some stuff, start transaction
    f(arg)
    ' Test some stuff, end transaction
End Sub

... but I was hoping to have this handle any number of arguments without having to have duplicate code for 0 args, 1 arg, 2 args, etc.

Is there a way of doing this?

[Edit] Thanks to Robert Fraser for the c# code. For reference, here's a translation to VB:

[Edit2] Corrected code. Unfortunately, there doesn't seem to be a way around having the separate "ActAsFunc" functions in vb. On the plus side, those are hidden from anyone using the closures and the closures are re-usable.

Public Shared Sub WrapFunc(ByVal f As Action)
    _WrapFunc(f)
End Sub
Public Shared Sub WrapFunc(Of T1)(ByVal f As Action(Of T1), ByVal arg1 As T1)
    _WrapFunc(Closure(f, arg1))
End Sub
Public Shared Sub WrapFunc(Of T1, T2)(ByVal f As Action(Of T1, T2), ByVal arg1 As T1, ByVal arg2 As T2)
    _WrapFunc(Closure(f, arg1, arg2))
End Sub

Private Shared Sub _WrapFunc(ByVal f As Action)
    ' Test some stuff, start transaction
    f()
    ' Test some stuff, end transaction
End Sub

Private Shared Function Closure(Of T1)(ByVal f As Action(Of T1), ByVal arg1 As T1) As Action
    Return New Action(Function() _ActAsFunc(f, arg1))
End Function
Private Shared Function Closure(Of T1, T2)(ByVal f As Action(Of T1, T2), ByVal arg1 As T1, ByVal arg2 As T2) As Action
    Return New Action(Function() _ActAsFunc(f, arg1, arg2))
End Function

Private Shared Function _ActAsFunc(Of T1)(ByVal f As Action(Of T1), ByVal arg1 As T1) As Object
    f(arg1) : Return Nothing
End Function
Private Shared Function _ActAsFunc(Of T1, T2)(ByVal f As Action(Of T1, T2), ByVal arg1 As T1, ByVal arg2 As T2) As Object
    f(arg1, arg2) : Return Nothing
End Function
+2  A: 

Nope. I wish there was, too; I do this all time.

Internally, MS uses text templates/code generation tools to generate the repetitive code. An option in C# is to create closures around the arguments and then just pass them to another function as a no-argument System.Action; I'm not sure if VB.NET support this.

Here's what I do in C#; hopefully someone with VB.NET experience can translate this:

public void add(Action action, EventFlags flags) { addInternal(action, flags); }
public void add<T1>(Action<T1> action, T1 p1, EventFlags flags) { addInternal(closure(action, p1), flags); }
public void add<T1, T2>(Action<T1, T2> action, T1 p1, T2 p2, EventFlags flags) { addInternal(closure(action, p1, p2), flags); }
public void add<T1, T2, T3>(Action<T1, T2, T3> action, T1 p1, T2 p2, T3 p3, EventFlags flags) { addInternal(closure(action, p1, p2, p3), flags); }
public void add<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action, T1 p1, T2 p2, T3 p3, T4 p4, EventFlags flags) { addInternal(closure(action, p1, p2, p3, p4), flags); }

private static Action closure<T1>(Action<T1> action, T1 p1) { return () => action(p1); }
private static Action closure<T1, T2>(Action<T1, T2> action, T1 p1, T2 p2) { return () => action(p1, p2); }
private static Action closure<T1, T2, T3>(Action<T1, T2, T3> action, T1 p1, T2 p2, T3 p3) { return () => action(p1, p2, p3); }
private static Action closure<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action, T1 p1, T2 p2, T3 p3, T4 p4) { return () => action(p1, p2, p3, p4); }
Robert Fraser
Having trouble getting my head around the closures at the moment, but this certainly looks very promising! I don't mind having to do separate function declarations if they're all one liners. I'll definitely look into this further.
Zarigani
Having gotten past my initial misconception that "closure" was some kind of built-in, I've hit a dead end on translating "return () => action(p1)" to vb. According to the docs "Visual Basic requires that a lambda expression return a value. As a result, the Action delegate cannot be used with a lambda expression in Visual Basic."Can the closure be phrased without this?
Zarigani
Found a way around it. Thanks for your help.
Zarigani
A: 

I haven't tried this with Generics, but what about a ParamArray ?

Public Shared Sub WrapFunc(Of T)(ByVal f As Action(Of T()), ByVal ParamArray args() As T)
    ' Test some stuff, start transaction
    f(args)
    ' Test some stuff, end transaction
End Sub
Steve Wortham
The line "f(args)" is a compile error in this case. f is an Action(Of T), not an Action(Of LotsOfT) unfortunately.
Zarigani
@Zarigani - Ah, OK. So what if you use Action(Of T()) instead? Wouldn't that work? (I just updated the code)
Steve Wortham
Yes, Action(Of T()) works, but only if all his arguments are always going to be the same type and all actions accept an array (or paramarray) as an argument. No way to do a variable number of types as far as I know though.
adam0101
@adam - That's kinda what I suspected. Judging by the original example I assumed that the parameters will be of the same type. In which case this could be made to work. But maybe that's a false assumption.
Steve Wortham
Unfortunately, the arguments can be of different types, but thanks for pointing out that I could potentially make this simplification.
Zarigani
A: 

You can't use the ParamArray?

  Public Shared Sub WrapFunc(Of T)(ByVal f As Action(Of T), ByVal ParamArray args() As T)
        ' Test some stuff, start transaction
        For Each arg As T In args
            f(arg)
        Next

        ' Test some stuff, end transaction
    End Sub
Benjamin Anderson
This would call the same function over and over with different parameters. Not a multi-parameter function like the OP wants.
Steve Wortham