【问题标题】:In C# 7 is it possible to deconstruct tuples as method arguments在 C# 7 中是否可以将元组解构为方法参数
【发布时间】:2017-05-26 03:57:42
【问题描述】:

例如我有

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

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

我想写这样的东西

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

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

这可以通过一些适当的符号来实现吗?

【问题讨论】:

    标签: tuples c#-7.0


    【解决方案1】:

    我正在使用 C# 8。一种干净的回调函数方式。

    [Snippet]

    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);
        });
    }
    

    输出

    MhamzaRajput
    23
    

    【讨论】:

    • 这正是 OP 在问题中的内容。
    【解决方案2】:

    这里有更简洁的语法变体,不需要任何额外的导入。不,它不能解决 cmets 中讨论的“飞溅”语法的愿望,但没有其他答案使用 ValueTuple 语法进行初始参数定义。

    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));
    }
    

    使用 lambda 表达式的调用并不那么冗长,仍然可以使用最少的语法检索 ValueTuple 组件:

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

    【讨论】:

      【解决方案3】:

      I. 带有 distinct-argsAction /Func 委托与 单 n 元组 参数的示例:

      // 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.演示以上示例的直接用法

      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.
      

      接下来两节中的示例以各自的非本地参数形式调用委托。要延迟方法调用或为缓存或延迟/多次调用情况保留一个适应的委托,请参阅 VI。和七。

      III. 将元组分散(“splat”)到多参数方法中。

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

      IV.将不同的参数传递给元组获取方法:

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

      V.上面(III)和(IV)中使用的扩展方法ScatterGather

      // 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. 奖金回合。如果您计划以替代形式多次调用一个元组或不同参数的委托,或者如果您还没有准备好实际调用它,您可能希望显式地从 tuple 预转换委托- 采用 等效的 distinct-args 代表的形式,反之亦然。您可以缓存转换后的委托以供以后多次或任意重用。

      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.附赠示例的扩展方法见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】:

        一种选择是使用 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);
        });
        

        【讨论】:

          【解决方案5】:

          我能得到的最接近的。

          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());
              }
          }
          

          【讨论】:

            【解决方案6】:

            有两种方法可以查看您的请求,但 C# 7.0 都不支持这两种方法。

            • 一种是将元组分解为参数:使用元组调用方法,并将元组的元素分解为方法的不同参数。您现在可以通过调用 M(tuple.first, tuple.second) 手动执行此操作。
            • 另一个是 lambda 参数中的解构:当使用参数调用 lambda 时,将该参数解构为元素并在 lambda 主体中使用这些元素。您现在可以通过将 lambda 定义为 x =&gt; { var (first, second) = x; Write(first); Write(second); } 来手动执行此操作。

            csharplang design repo 中有一些提案正在讨论中。

            【讨论】:

              【解决方案7】:

              您可以将其缩短为:

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

              希望有一天我们可以将元组中的参数分配给方法调用:

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

              但目前没有。

              【讨论】:

              • 通过一些扩展方法 (public static object[] ToArray&lt;T1, T2&gt;(this ValueTuple&lt;T1, T2&gt; t) { return new object[]{ t.Item1, t.Item2 }; }),您可以使用 fn.DynamicInvoke(("hello",10).ToArray()); - 当然,此时扩展 Invoke 可能更有意义。
              • 很好的答案。我试图在文档中找到这种语法但没有成功。您能否提供解释它的文档的链接?
              • @AndriiLitvinov,你指的是什么?
              • 我真的不知道它在文档的哪个位置。但是您始终可以在() 之间指定参数,并在名称前加上类型的前缀。 ((string s, int i) t) =&gt; 在这方面与 `(string s) => 没有什么不同。
              • fn(@("hello", 10)); 没有意义。它仍在向函数传递一个元组,所以只需 fn(("hello", 10)); 就足够了。我们不需要任何特殊的语法,只需要更好的类型推断。想象一下最简单的test( ((str, inte)) =&gt;,它将表明 (str,inte) 是一个未命名的元组类型参数。未命名参数允许将 strinte 特殊处理为从传入元组解构的局部自动变量的名称。这与test( ((string, int)p) =&gt; 不同,其中参数是名称,(string,int) 是具有字段 Item1,Item2 的给定类型的非元组。
              猜你喜欢
              • 2017-09-22
              • 1970-01-01
              • 1970-01-01
              • 2018-05-28
              • 2010-10-05
              • 2016-07-26
              • 1970-01-01
              • 2017-04-15
              相关资源
              最近更新 更多