在 C# 7 中是否可以将元组解构为方法参数
In C# 7 is it possible to deconstruct tuples as method arguments
比如我有
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);
});
这可以用一些适当的符号吗?
您可以将其缩短为:
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);
});
但目前还没有。
有两种查看请求的方法,但 C# 7.0 均不支持。
- 一种是将元组分解为参数:使用元组调用方法,并将元组的元素分解为方法的不同参数。您今天可以通过调用
M(tuple.first, tuple.second)
. 手动执行此操作
- 另一个是 lambda 参数中的解构:当使用参数调用 lambda 时,将该参数解构为元素并在 lambda 主体中使用这些元素。您现在可以通过将 lambda 定义为
x => { var (first, second) = x; Write(first); Write(second); }
. 来手动执行此操作
csharplang 设计库中正在讨论一些提案。
- https://github.com/dotnet/csharplang/issues/424(将元组拆分为参数)
- https://github.com/dotnet/csharplang/issues/258(在lambda参数中解构)
我能得到的最接近的。
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());
}
}
一种选择是使用 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);
});
I. Action
/Func
代表 distinct-args 与 单个 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.
接下来两节中的示例以各自的非本机参数形式调用委托。要延迟方法调用或保留适合的委托用于缓存或 delayed/multiple-call 场景,请参阅 VI。和 VII.
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.扩展方法Scatter
和Gather
在上面(III)和(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.奖金回合。如果您计划以其替代形式多次调用元组或不同参数获取委托,或者如果您还没有准备好实际调用它,您可能希望显式预转换来自 [=37= 的委托]tuple-taking 形式到 等效的 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);
这里有更简洁的语法变体,不需要任何额外的导入。不,它没有解决评论中讨论的 "splatter" 语法的愿望,但没有其他答案将 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);
});
我正在使用 C# 8。回调函数的简洁方式。
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
比如我有
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);
});
这可以用一些适当的符号吗?
您可以将其缩短为:
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);
});
但目前还没有。
有两种查看请求的方法,但 C# 7.0 均不支持。
- 一种是将元组分解为参数:使用元组调用方法,并将元组的元素分解为方法的不同参数。您今天可以通过调用
M(tuple.first, tuple.second)
. 手动执行此操作
- 另一个是 lambda 参数中的解构:当使用参数调用 lambda 时,将该参数解构为元素并在 lambda 主体中使用这些元素。您现在可以通过将 lambda 定义为
x => { var (first, second) = x; Write(first); Write(second); }
. 来手动执行此操作
csharplang 设计库中正在讨论一些提案。
- https://github.com/dotnet/csharplang/issues/424(将元组拆分为参数)
- https://github.com/dotnet/csharplang/issues/258(在lambda参数中解构)
我能得到的最接近的。
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());
}
}
一种选择是使用 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);
});
I. Action
/Func
代表 distinct-args 与 单个 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.
接下来两节中的示例以各自的非本机参数形式调用委托。要延迟方法调用或保留适合的委托用于缓存或 delayed/multiple-call 场景,请参阅 VI。和 VII.
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.扩展方法Scatter
和Gather
在上面(III)和(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.奖金回合。如果您计划以其替代形式多次调用元组或不同参数获取委托,或者如果您还没有准备好实际调用它,您可能希望显式预转换来自 [=37= 的委托]tuple-taking 形式到 等效的 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);
这里有更简洁的语法变体,不需要任何额外的导入。不,它没有解决评论中讨论的 "splatter" 语法的愿望,但没有其他答案将 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);
});
我正在使用 C# 8。回调函数的简洁方式。
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