Co sprawia, że ​​kowariant ValueTuple?

35

Kompiluje się to poprawnie w C # 7.3 (Framework 4.8):

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

Wiem, że jest to cukier składniowy dla następujących składników, który również poprawnie się kompiluje:

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

Wygląda więc na to, że ValueTuples można przypisywać kowariantnie , co jest niesamowite !

Niestety, nie rozumiem dlaczego : miałem wrażenie, że C # wspierał kowariancję tylko w interfejsach i delegatach . ValueTypeNie jest.

W rzeczywistości, gdy próbuję powielić tę funkcję za pomocą własnego kodu, nie udaje mi się:

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>'

Dlaczego więc można ValueTuplego przypisać kowariantnie, MyValueTuplea nie można?

Heinzi
źródło
2
Jest to prawdopodobnie specjalne potraktowanie przez kompilator, podobnie jak sposób przypisania nulldo Nullable<T>choćby to jest struct.
juharr
2
W rzeczywistości zdekompilowany kod wygląda następująco dla drugiego zadania:ValueTuple<object, string> o = new ValueTuple<object, string>(s.Item1, s.Item2);
Lasse V. Karlsen
2
Krotki są dziwne i są całkowicie zaimplementowane w interfejsie kompilatora c #, zamiast polegać na podstawowej reprezentacji CLR. Ten operator przydziału nie robi tego, co myślisz.
Jeremy Lakeman,
2
Dodaj domyślny operator public static implicit operator MyValueTuple<A, B>(MyValueTuple<string, string> v) { throw new NotImplementedException(); }dekonstruujący przypisanie. Nawiasem mówiąc, świetne pytanie!
Çöđěxěŕ
1
@ Çöđěxěŕ bulls eye! dzięki czemu można go skompilować, a wyjątek jest zgłaszany zgodnie z oczekiwaniami
Mong Zhu,

Odpowiedzi:

25

Uważam, że to, co się tutaj naprawdę dzieje, to zadanie restrukturyzacji. Przypisanie krotka będzie próbować niejawnie przekonwertować jego składników, a jak to jest możliwe, aby przypisać stringdo object, że to, co dzieje się tutaj.

Język obsługuje przypisywanie między krotkami, które mają tę samą liczbę elementów, przy czym każdy prawy element boczny może być niejawnie przekonwertowany na odpowiadający mu lewy element boczny. Inne konwersje nie są uwzględniane dla zadań.

Źródło

Zobacz na sharplab.io

Gareth Latty
źródło
4
Właśnie wypróbowałem to na SharpLab i na pewno robi dokładnie to .
John,
3
Aby to wzmocnić, w oryginalnym przykładzie OP, jeśli zmienisz sna pisanie (string, object), spowoduje to błąd konwersji, wskazując, że zachodzi niejawna konwersja między elementami, a łańcuch można niejawnie przekonwertować na łańcuch, ale nie odwrotnie.
Eric Lease,