I have a method that manipulates a value tuple with multiple fields. That tuple is returned from another method and deconstructed, in order to simplify further usages of its fields:
var (a, b, c) = GetValueTuple();
However, now I need to pass the entire tuple down to another method that expects it:
private void ExpectsTuple((a, b, c) tuple) { ... }
this.ExpectsTuple(/* need to pass tuple here */);
Is there an easy or clean way to "reconstruct" or pass down the entire tuple structure, without having to create a new one? I tried to name the tuple but that throws a compilation error:
var (a, b, c) tuple = GetValueTuple();
I know I could just do ExpectsTuple((a, b, c))
but I wonder if that's gonna unnecessarily create a copy of my structure and if there's a cleaner way to do it.
Tuple types are value types; tuple elements are public fields. That makes tuples mutable value types.
A ValueTuple is a structure introduced in C# 7.0. A ValueTuple overcomes two major limitations of tuples—namely, that they are inefficient and that they must be referenced as Item1, Item2, etc. That is, ValueTuples are both performant and referenceable by names the programmer chooses.
ValueTuple is the underlying type used for the C#7 tuples. They cannot be null as they are value types. You can test them for default though, but that might actually be a valid value.
C# tuple is a data structure that provides an easy way to represent a single set of data. The System. Tuple class provides static methods to create tuple objects.
If you deconstruct it without storing the original; the original is gone; maybe store the original? i.e. var tuple = GetValueTuple()
? You can then either access tuple.Foo
etc, or just deconstruct it afterwards:
So:
var tuple = GetValueTuple();
var (a, b, c) = tuple;
// not shown: something involving a, b, c
// ...
ExpectsTuple(tuple);
or
var tuple = GetValueTuple();
// not shown: something involving tuple.Foo, tuple.Bar, etc
// ...
ExpectsTuple(tuple);
Edit: the following paragram may be incorrect; in
may have been fine here; leaving for completeness, but maybe the trick would have been:
void ExpectsTuple(in (...the tuple type...) tuple)
{...}
// ...
ExpectsTuple(tuple); // the "in" here is implicit
As for creating a copy of the structure: it is going to do that either way - because of the stack semantics. The only question is how efficiently it can do it (does it have to execute a constructor? or is it just a blit?). Note that you can't use in
to avoid this extra copy, because value-tuples are not readonly struct
, so using the in
will simply copy the tuple on the stack and then pass the address of the copy; you might as well just pass the copy on the stack. Technically you could use ref
to avoid the copy, but... that is awkward, and probably not worth it in most cases. It also leaves your code at the mercy of whatever ExpectsTuple(ref tuple)
chooses to do to the data.
Personally, I'd use the tuple.Foo
etc approach; since tuples provide direct access to the fields, this is actually more direct than it might look, especially after the JIT has done what it needs.
From comments:
Could you elaborate on "how efficiently it can do it (does it have to execute a constructor? or is it just a blit?)"
What I mean here is: when you just pass a pre-existing value, all it has to do is load the value from the stack - which is just a fast memory copy; however, if you've deconstructed it, it needs to reconstruct it, which means: running the ValueTuple<,,>
constructor - with whatever logic that has; you can see that here, by looking at the difference between PassDirect
and DeconstructAndReconstruct
on the right hand side (which is an interpretation of the code after the compiler has finished with it). You can see that ExpectsTuple((a, b, c))
ends up running ExpectsTuple(new ValueTuple<int, DateTime, string>(item, item2, item3))
; the code for this constructor isn't complicated, but it is a lot more complicated than just "load the value I already knew about".
I should also clarify: the code in the right hand pane isn't exactly right - there isn't actually another local with the value; to see what actually happens, you need to look at the IL view - the actual local variables are declared at the top; see:
.locals init (
[0] valuetype [System.Private.CoreLib]System.ValueTuple`3<int32, valuetype [System.Private.CoreLib]System.DateTime, string> tuple
)
and
.locals init (
[0] int32 a,
[1] valuetype [System.Private.CoreLib]System.DateTime b,
[2] string c
)
Note that the second one does not have both, i.e. it isn't
.locals init (
[0] valuetype [System.Private.CoreLib]System.ValueTuple`3<int32, valuetype [System.Private.CoreLib]System.DateTime, string> tuple
[1] int32 a,
[2] valuetype [System.Private.CoreLib]System.DateTime b,
[3] string c
)
(despite that being what the C# view would suggest)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With