In C# 7 is it possible to deconstruct tuples as method arguments

前端 未结 7 819

For example I have

private void test(Action> fn)
{
    fn((\"hello\", 10));
}

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


        
相关标签:
7条回答
  • 2020-12-09 15:11

    There are two ways of looking at your request, but neither is supported in C# 7.0.

    • One is splatting of tuples into arguments: calling a method with a tuple, and have the elements of the tuple splat into distinct arguments of the method. You can do this today manually by invoking M(tuple.first, tuple.second).
    • The other is deconstruction in lambda parameters: when a lambda is invoked with a parameter, deconstruct that parameters into elements and use those elements in the lambda body. You can do this today manually by defining a lambda as x => { var (first, second) = x; Write(first); Write(second); }.

    There are proposals being discussed in the csharplang design repo.

    • https://github.com/dotnet/csharplang/issues/424 (splatting of tuples into arguments)
    • https://github.com/dotnet/csharplang/issues/258 (deconstruction in lambda parameters)
    0 讨论(0)
  • 2020-12-09 15:14

    I. Examples of Action /Func delegates with 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. 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<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. 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<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);
    
    0 讨论(0)
  • 2020-12-09 15:18

    I'm using C# 8. A clean way for a callback function.

    public static void CallBackTest( Action<(string Name, int Age)> fn)
    {
        fn(("MhamzaRajput", 23));
    }
    
    public static void Main()
    {
        CallBackTest((t) =>
        {
            var ( Name, Age ) = t;
    
            Console.WriteLine(t.Name);
            Console.WriteLine(t.Age);
        });
    }
    

    Output

    MhamzaRajput
    23
    
    0 讨论(0)
  • 2020-12-09 15:20

    You can shorten it to:

    void test( Action<ValueTuple<string, int>> fn)
    {
        fn(("hello", 10));
    }
    
    test(((string s, int i) t) =>
    {
        Console.WriteLine(t.s);
        Console.WriteLine(t.i);
    });
    

    Hopefully, one day we might be able to splat the parameters from a tuple to the method invocation:

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

    But not at the moment.

    0 讨论(0)
  • 2020-12-09 15:20

    One option is to use 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);
    });
    
    0 讨论(0)
  • 2020-12-09 15:27

    The closest that I could get.

    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());
        }
    }
    
    0 讨论(0)
提交回复
热议问题