Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reference a deconstructed value tuple without making a copy of it

Tags:

c#

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.

like image 534
parakeet Avatar asked Jun 16 '20 15:06

parakeet


People also ask

Is tuple reference or value type?

Tuple types are value types; tuple elements are public fields. That makes tuples mutable value types.

What is System ValueTuple?

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.

Can tuple be null C#?

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.

What is AC tuple?

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.


1 Answers

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)

like image 152
Marc Gravell Avatar answered Sep 25 '22 00:09

Marc Gravell