For example I have
private void test(Action> fn)
{
fn((\"hello\", 10));
}
test(t =>
{
var (s, i) = t;
C
I. Examples of Action
/Func
delegates with distinct-args vs. single n-tuple arguments:
// 1. Action with 3 distinct 'int' parameters
Action ArgsAction = (i1, i2, i3) => i1 += i2 += i3;
// 2. Func with 3 distinct 'int' parameters, returning 'long'
Func ArgsFunc = (i1, i2, i3) => (long)i1 + i2 + i3;
// 3. Action with a single 3-tuple parameter
Action<(int, int, int)> TupleAction = args => args.Item1 += args.Item2 += args.Item3;
// 4. Action with a single 3-tuple parameter, returning 'long'
Func<(int, int, int), long> TupleFunc = args => (long)args.Item1 + args.Item2 + args.Item3;
II. Demonstrate direct usage of the above examples
long r;
// pass distinct params to multi-arg methods
ArgsAction(1, 2, 3); // 1.
r = ArgsFunc(1, 2, 3); // 2.
// pass tuple to tuple-taking methods
TupleAction((1, 2, 3)); // 3.
r = TupleFunc((1, 2, 3)); // 4.
The examples in the next two sections invoke the delegates in their respective non-native argument forms. To delay the method call or to retain an adapted delegate for caching or delayed/multiple-call secenarios, see VI. and VII.
III. disperse ("splat") a tuple into multi-arg methods.
(1, 2, 3).Scatter(ArgsAction); // 1.
r = (1, 2, 3).Scatter(ArgsFunc); // 2.
IV. pass distinct args into tuple-taking methods:
TupleAction.Gather(1, 2, 3); // 3.
r = TupleFunc.Gather(1, 2, 3); // 4.
V. Extension methods Scatter
and Gather
used above in (III) and (IV):
// disperse n-tuple into Action arguments
public static void Scatter(in this (T0 i0, T1 i1) t, Action a) => a(t.i0, t.i1);
public static void Scatter(in this (T0 i0, T1 i1, T2 i2) t, Action a) => a(t.i0, t.i1, t.i2);
public static void Scatter(in this (T0 i0, T1 i1, T2 i2, T3 i3) t, Action a) => a(t.i0, t.i1, t.i2, t.i3);
// disperse n-tuple into Func arguments
public static TResult Scatter(in this (T0 i0, T1 i1) t, Func f) => f(t.i0, t.i1);
public static TResult Scatter(in this (T0 i0, T1 i1, T2 i2) t, Func f) => f(t.i0, t.i1, t.i2);
public static TResult Scatter(in this (T0 i0, T1 i1, T2 i2, T3 i3) t, Func f) => f(t.i0, t.i1, t.i2, t.i3);
// accumulate 'n' distinct args and pass into Action as an n-tuple
public static void Gather(this Action<(T0, T1)> a, T0 i0, T1 i1) => a((i0, i1));
public static void Gather(this Action<(T0, T1, T2)> a, T0 i0, T1 i1, T2 i2) => a((i0, i1, i2));
public static void Gather(this Action<(T0, T1, T2, T3)> a, T0 i0, T1 i1, T2 i2, T3 i3) => a((i0, i1, i2, i3));
// accumulate 'n' distinct args and pass into Func as an n-tuple
public static TResult Gather(this Func<(T0, T1), TResult> f, T0 i0, T1 i1) => f((i0, i1));
public static TResult Gather(this Func<(T0, T1, T2), TResult> f, T0 i0, T1 i1, T2 i2) => f((i0, i1, i2));
public static TResult Gather(this Func<(T0, T1, T2, T3), TResult> f, T0 i0, T1 i1, T2 i2, T3 i3) => f((i0, i1, i2, i3));
VI. Bonus round. If you plan to call a tuple- or distinct-arg-taking delegate multiple times in its alternate form, or if you're not ready to actually invoke it yet, you may wish to explicitly pre-convert the delegate from tuple-taking form to the equivalent distinct-args delegate, or vice-versa. You can cache the converted delegate for multiple or arbitrary later re-use.
var ga = ArgsAction.ToGathered(); // 1.
// later...
ga((1, 2, 3));
// ...
ga((4, 5, 6));
var gf = ArgsFunc.ToGathered(); // 2.
// later...
r = gf((1, 2, 3));
// ...
r = gf((4, 5, 6));
var sa = TupleAction.ToScattered(); // 3.
// later...
sa(1, 2, 3);
// ...
sa(4, 5, 6);
var sf = TupleFunc.ToScattered(); // 4.
// later...
r = sf(1, 2, 3);
// ...
r = sf(4, 5, 6);
// of course these approaches also supports in-situ usage:
ArgsAction.ToGathered()((1, 2, 3)); // 1.
r = ArgsFunc.ToGathered()((1, 2, 3)); // 2.
TupleAction.ToScattered()(1, 2, 3); // 3.
r = TupleFunc.ToScattered()(1, 2, 3); // 4.
VII. Extension methods for bonus examples shown in VI.
// convert tuple-taking Action delegate to distinct-args form
public static Action ToScattered(this Action<(T0, T1)> a) => (i0, i1) => a((i0, i1));
public static Action ToScattered(this Action<(T0, T1, T2)> a) => (i0, i1, i2) => a((i0, i1, i2));
public static Action ToScattered(this Action<(T0, T1, T2, T3)> a) => (i0, i1, i2, i3) => a((i0, i1, i2, i3));
// convert tuple-taking Func delegate to its distinct-args form
public static Func ToScattered(this Func<(T0, T1), TResult> f) => (i0, i1) => f((i0, i1));
public static Func ToScattered(this Func<(T0, T1, T2), TResult> f) => (i0, i1, i2) => f((i0, i1, i2));
public static Func ToScattered(this Func<(T0, T1, T2, T3), TResult> f) => (i0, i1, i2, i3) => f((i0, i1, i2, i3));
// convert distinct-args Action delegate to tuple-taking form
public static Action<(T0, T1)> ToGathered(this Action a) => t => a(t.Item1, t.Item2);
public static Action<(T0, T1, T2)> ToGathered(this Action a) => t => a(t.Item1, t.Item2, t.Item3);
public static Action<(T0, T1, T2, T3)> ToGathered(this Action a) => t => a(t.Item1, t.Item2, t.Item3, t.Item4);
// convert distinct-args Func delegate to its tuple-taking form
public static Func<(T0, T1), TResult> ToGathered(this Func f) => t => f(t.Item1, t.Item2);
public static Func<(T0, T1, T2), TResult> ToGathered(this Func f) => t => f(t.Item1, t.Item2, t.Item3);
public static Func<(T0, T1, T2, T3), TResult> ToGathered(this Func f) => t => f(t.Item1, t.Item2, t.Item3, t.Item4);