web-dev-qa-db-fra.com

En C # 7 est-il possible de déconstruire les tuples comme arguments de méthode

Par exemple, j'ai

private void test(Action<ValueTuple<string, int>> fn)
{
    fn(("hello", 10));
}

test(t => 
 {
    var (s, i) = t;
    Console.WriteLine(s);
    Console.WriteLine(i);
});

J'aimerais écrire quelque chose comme ça

private void test(Action<ValueTuple<string, int>> fn)
{
    fn(("hello", 10));
}

test((s,i) => 
{
    Console.WriteLine(s);
    Console.WriteLine(i);
});

Est-ce possible avec une notation appropriée?

26
bradgonesurfing

Vous pouvez le raccourcir pour:

void test( Action<ValueTuple<string, int>> fn)
{
    fn(("hello", 10));
}

test(((string s, int i) t) =>
{
    Console.WriteLine(t.s);
    Console.WriteLine(t.i);
});

Avec un peu de chance, un jour, nous pourrons être en mesure de répartir les paramètres d'un tuple à l'invocation de la méthode:

void test(Action<ValueTuple<string, int>> fn)
{
    fn(@("hello", 10)); // <-- made up syntax
}

test((s, i) =>
{
    Console.WriteLine(s);
    Console.WriteLine(i);
});

Mais pas pour le moment.

19
Paulo Morgado

I. Exemples de Action/Func délégués avec distinct-args vs single n-Tuple arguments :

// 1. Action with 3 distinct 'int' parameters
Action<int, int, int> ArgsAction = (i1, i2, i3) => i1 += i2 += i3;

// 2. Func with 3 distinct 'int' parameters, returning 'long'
Func<int, int, int, long> 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. Démontrez l'utilisation directe des exemples ci-dessus

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.

Les exemples des deux sections suivantes invoquent les délégués dans leurs formes d'argument non natives respectives. Pour retarder l'appel de méthode ou pour conserver un délégué adapté pour la mise en cache ou les secénarios d'appel différé/multiple, voir VI. et VII.

III. disperse ("splat") un Tuple en méthodes multi-arg.

(1, 2, 3).Scatter(ArgsAction);      // 1.

r = (1, 2, 3).Scatter(ArgsFunc);    // 2.

IV. passez des arguments distincts dans les méthodes de prise de tuple:

TupleAction.Gather(1, 2, 3);        // 3.

r = TupleFunc.Gather(1, 2, 3);      // 4.

V. Méthodes d'extension Scatter et Gather utilisées ci-dessus dans (III) et (IV):

// disperse n-Tuple into Action arguments
public static void Scatter<T0, T1>(in this (T0 i0, T1 i1) t, Action<T0, T1> a) => a(t.i0, t.i1);
public static void Scatter<T0, T1, T2>(in this (T0 i0, T1 i1, T2 i2) t, Action<T0, T1, T2> a) => a(t.i0, t.i1, t.i2);
public static void Scatter<T0, T1, T2, T3>(in this (T0 i0, T1 i1, T2 i2, T3 i3) t, Action<T0, T1, T2, T3> a) => a(t.i0, t.i1, t.i2, t.i3);

// disperse n-Tuple into Func arguments
public static TResult Scatter<T0, T1, TResult>(in this (T0 i0, T1 i1) t, Func<T0, T1, TResult> f) => f(t.i0, t.i1);
public static TResult Scatter<T0, T1, T2, TResult>(in this (T0 i0, T1 i1, T2 i2) t, Func<T0, T1, T2, TResult> f) => f(t.i0, t.i1, t.i2);
public static TResult Scatter<T0, T1, T2, T3, TResult>(in this (T0 i0, T1 i1, T2 i2, T3 i3) t, Func<T0, T1, T2, T3, TResult> 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<T0, T1>(this Action<(T0, T1)> a, T0 i0, T1 i1) => a((i0, i1));
public static void Gather<T0, T1, T2>(this Action<(T0, T1, T2)> a, T0 i0, T1 i1, T2 i2) => a((i0, i1, i2));
public static void Gather<T0, T1, T2, T3>(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<T0, T1, TResult>(this Func<(T0, T1), TResult> f, T0 i0, T1 i1) => f((i0, i1));
public static TResult Gather<T0, T1, T2, TResult>(this Func<(T0, T1, T2), TResult> f, T0 i0, T1 i1, T2 i2) => f((i0, i1, i2));
public static TResult Gather<T0, T1, T2, T3, TResult>(this Func<(T0, T1, T2, T3), TResult> f, T0 i0, T1 i1, T2 i2, T3 i3) => f((i0, i1, i2, i3));

VI. Tour bonus. Si vous prévoyez d'appeler plusieurs fois un délégué prenant un tuple ou un argument distinct dans sa forme alternative, ou si vous n'êtes pas encore prêt à l'invoquer, vous souhaiterez peut-être explicitement pré-convertir le délégué de Tuple-take form to the equivalent distinct-args delegate, ou vice-versa. Vous pouvez mettre en cache le délégué converti pour une réutilisation ultérieure multiple ou arbitraire.

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. Méthodes d'extension pour les exemples de bonus présentés dans VI.

// convert Tuple-taking Action delegate to distinct-args form
public static Action<T0, T1> ToScattered<T0, T1>(this Action<(T0, T1)> a) => (i0, i1) => a((i0, i1));
public static Action<T0, T1, T2> ToScattered<T0, T1, T2>(this Action<(T0, T1, T2)> a) => (i0, i1, i2) => a((i0, i1, i2));
public static Action<T0, T1, T2, T3> ToScattered<T0, T1, T2, T3>(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<T0, T1, TResult> ToScattered<T0, T1, TResult>(this Func<(T0, T1), TResult> f) => (i0, i1) => f((i0, i1));
public static Func<T0, T1, T2, TResult> ToScattered<T0, T1, T2, TResult>(this Func<(T0, T1, T2), TResult> f) => (i0, i1, i2) => f((i0, i1, i2));
public static Func<T0, T1, T2, T3, TResult> ToScattered<T0, T1, T2, T3, TResult>(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<T0, T1>(this Action<T0, T1> a) => t => a(t.Item1, t.Item2);
public static Action<(T0, T1, T2)> ToGathered<T0, T1, T2>(this Action<T0, T1, T2> a) => t => a(t.Item1, t.Item2, t.Item3);
public static Action<(T0, T1, T2, T3)> ToGathered<T0, T1, T2, T3>(this Action<T0, T1, T2, T3> 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<T0, T1, TResult>(this Func<T0, T1, TResult> f) => t => f(t.Item1, t.Item2);
public static Func<(T0, T1, T2), TResult> ToGathered<T0, T1, T2, TResult>(this Func<T0, T1, T2, TResult> f) => t => f(t.Item1, t.Item2, t.Item3);
public static Func<(T0, T1, T2, T3), TResult> ToGathered<T0, T1, T2, T3, TResult>(this Func<T0, T1, T2, T3, TResult> f) => t => f(t.Item1, t.Item2, t.Item3, t.Item4);
4
Glenn Slayden

Il existe deux façons d'examiner votre demande, mais aucune n'est prise en charge dans C # 7.0.

  • La première consiste à répartir les tuples en arguments: appeler une méthode avec un Tuple et avoir les éléments du Tuple splat en arguments distincts de la méthode. Vous pouvez le faire aujourd'hui manuellement en appelant M(Tuple.first, Tuple.second).
  • L'autre est la déconstruction des paramètres lambda: lorsqu'un lambda est invoqué avec un paramètre, déconstruisez ces paramètres en éléments et utilisez ces éléments dans le corps lambda. Vous pouvez le faire aujourd'hui manuellement en définissant un lambda comme x => { var (first, second) = x; Write(first); Write(second); }.

Des propositions sont en cours de discussion dans le référentiel de conception de csharplang.

3
Julien Couvreur

Une option consiste à utiliser TupleSplatter ( https://github.com/chartjunk/TupleSplatter ):

using TupleSplatter;

void test(Action<string, int> fn)
{
    fn.SplatInvoke(("hello", 10));
    // or
    ("hello", 10).Splat(fn);
}

test((s,i) => {
    Console.WriteLine(s);
    Console.WriteLine(i);
});
3
Joona Luoma

Voici des variantes de syntaxe plus concises qui ne nécessitent aucune importation supplémentaire. Non, cela ne résout pas les souhaits de syntaxe "éclaboussures" discutés dans les commentaires, mais aucune autre réponse n'a utilisé la syntaxe ValueTuple pour la définition de paramètre initiale.

void test(Action<(string, int)> fn)
{
    fn(("hello", 10));
}

// OR using optional named ValueTuple arguments
void test(Action<(string Word, int num)> fn)
{
    fn((Word: "hello", num: 10));
}

L'invocation à l'aide d'une expression lambda n'est pas très détaillée et les composants ValueTuple peuvent toujours être récupérés à l'aide d'une syntaxe minimale:

test( ((string, int) t) => {
    var (s, i) = t;

    Console.WriteLine(s);
    Console.WriteLine(i);
});
1
C Perkins

Le plus proche que je pouvais obtenir.

public static class DeconstructExtensions
{
    public static Action<T1, T2> Deconstruct<T1, T2>(this Action<(T1, T2)> action) => (a, b) => action((a, b));
    public static Action<(T1, T2)> Construct<T1, T2>(this Action<T1, T2> action) => a => action(a.Item1, a.Item2);
}

class Test
{
    private void fn((string, int) value) { }

    private void test(Action<ValueTuple<string, int>> fn)
    {
        fn(("hello", 10));
    }

    private void Main()
    {
        var action = new Action<string, int>((s, i) =>
        {
            Console.WriteLine(s);
            Console.WriteLine(i);
        });

        test(action.Construct());
    }
}
1
Skarllot