What makes ValueTuple covariant?
Asked Answered
C

1

40

This compiles correctly in C# 7.3 (Framework 4.8):

(string, string) s = ("a", "b");
(object, string) o = s;

I know that this is syntactic sugar for the following, which also compiles correctly:

ValueTuple<string, string> s = new ValueTuple<string, string>("a", "b");
ValueTuple<object, string> o = s;

So, it appears that ValueTuples can be assigned covariantly, which is awesome!

Unfortunately, I don't understand why: I was under the impression that C# only supported covariance on interfaces and delegates. ValueType is neither.

In fact, when I try to duplicate this feature with my own code, I fail:

struct MyValueTuple<A, B>
{
    public A Item1;
    public B Item2;

    public MyValueTuple(A item1, B item2)
    {
        Item1 = item1;
        Item2 = item2;
    }
}

...

MyValueTuple<string, string> s = new MyValueTuple<string, string>("a", "b");
MyValueTuple<object, string> o = s;
// ^ Cannot implicitly convert type 'MyValueTuple<string, string>' to 'MyValueTuple<object, string>'

So, why can ValueTuples be assigned covariantly, but MyValueTuples can't?

Caviness answered 19/12, 2019 at 14:7 Comment(5)
This is likely special treatment by the compiler, just like how you can assign null to a Nullable<T> even though it's a struct.Nicks
Actually, the decompiled code looks like this for the second assignment: ValueTuple<object, string> o = new ValueTuple<object, string>(s.Item1, s.Item2);Pharmacy
Tuples are weird and are implemented entirely in the c# compiler front end, instead of relying on an underlying CLR representation. That assignment operator isn't doing what you think.Area
Add an implicit operator public static implicit operator MyValueTuple<A, B>(MyValueTuple<string, string> v) { throw new NotImplementedException(); } its deconstructing the assignment. Also, great question by the way!Unavoidable
@Çöđěxěŕ bulls eye! that makes it compileable, and the exception is thrown as expectedVacate
A
30

I believe what is actually happening here is a destructuring assignment. Tuple assignment will try to implicitly convert its components, and as it is possible to assign string to object, that is what happens here.

The language supports assignment between tuple types that have the same number of elements, where each right-hand side element can be implicitly converted to its corresponding left-hand side element. Other conversions aren't considered for assignments.

Source

See it on sharplab.io

Appreciate answered 19/12, 2019 at 14:12 Comment(2)
I just tried it on SharpLab and sure enough it does exactly that.Disruption
Just to reinforce that, in OP's original example, if you change s to type (string, object) it results in a conversion error, indicating that implicit conversion is taking place between the items, and string can be implicitly converted to string, but not vice versa.Stadia

© 2022 - 2024 — McMap. All rights reserved.