C#7 value tuple/deconstruction asymmetry
Asked Answered
W

3

10

Fiddle here.

Given a function (string a, string b) F(), you can deconstruct the tuple it returns:

var (a, b) = F();

(string c, string d) = F();

Or you can just assign it:

var (a, b) e = F();

(string a, string b) f = F();

var g = F();  //  One of these things is not like the others.

Class deconstructors behave like the first case. Given a class C with Deconstructor(out string a, out string b):

var c = new C();

var (h, i) = c;

(string j, string k) = c;

But the compiler won't use the deconstructor to implicitly convert it to a tuple:

//  Cannot implicitly convert type 'C' to '(string a, string b)'
var (a, b) l = c;

Obviously you can mechanically write an implicit conversion based on the deconstructor:

public static implicit operator (string a, string b) (C c)
{
    c.Deconstruct(out string a, out string b);
    return (a, b);
}

Notwithstanding the visual similarity in the syntax between the deconstruction and assignment cases, assigning a reference to a tuple is not the same as deconstructing a class into variables and then putting them in a new tuple. However, you can implicitly convert (int x, int y) to (double x, double y). Value tuples are the kind of syntactic-sugar feature where it does what it looks like it does, and never mind the implementation details.

If I thought of this, the C# team thought of it, and if they chose not to add "magic" support for the implicit conversion, they had a good reason1.

Is there a positive reason why doing the implicit conversion automatically would have been a bad idea?

Or is it one of those features that just wasn't regarded as valuable enough to justify the cost?


Here's the code from that fiddle:

public class Program
{
    public static void Main()
    {
        (string a, string b) = F();

        (string a, string b) ab = F();

        Console.WriteLine($"a: {a} b: {b} ab: {ab}");


        var c = new C();

        (string d, string e) = c;

        //  Cannot implicitly convert type 'C' to '(string a, string b)'
        (string a, string b) f = c;

        Console.WriteLine($"d: {d} e: {e} f: {f}");

        //  Covariance
        (object c, object d) g = F();
        //  Implicit conversion
        (double x, double y) t = G();
    }

    public static (string a, string b) F() 
        => ("A", "B");

    public static (int x, int y) G() 
        => (0, 1);
}

public class C
{
    public String A = "A";
    public String B = "B";

    public void Deconstruct(out String a, out String b)
    {
        a = A;
        b = B;
    }
}

1 The C# team may not be smarter than everybody, but I've never lost money betting they were at least as smart as me.

Westernmost answered 11/5, 2017 at 16:6 Comment(1)
Obviously, you don't want implicit deconstruction the same way you usually don't want implicit conversion from a class to another one. Also, it does not make much sense to use both tuple and deconstructed variables from the same source. It would only make the code harder to understand. And as a language designer, it make sense to start strictier anyway and then eventually generalize than the contrary. For example, the fact that reference types are nullable by default is C# is now considered as one of the biggest design mistake...Barong
C
7

The ability to have deconstructs act like implicit converters was something that was asked for (by me, so I'm biased here) before C# 7 was released. The response from the team was (as I read it anyway) that it was asked for too near to the C# 7 release and would have taken too long to implement, so wasn't up for consideration. As it would now be a breaking change, it's not something that will ever happen.

Please see the "Allow Deconstruct and implicit operator to both support deconstruction and conversion to tuple types" roslyn repo issue for the actual discussion on the matter.

Caryl answered 12/5, 2017 at 7:39 Comment(0)
L
7

(string a, string b) ab declares a single variable of the tuple type (string a, string b) named ab. This lets you write ab.a or ab.b, but not a or b.

(string a, string b) f = c; tries to convert an unrelated C type to this tuple type. Unless you write a cast, there is no way for that to happen.

Specifically, as the name implies, destructuring only lets you assign to variables; it does not let you convert to an unrelated type.

Lawton answered 11/5, 2017 at 16:10 Comment(0)
C
7

The ability to have deconstructs act like implicit converters was something that was asked for (by me, so I'm biased here) before C# 7 was released. The response from the team was (as I read it anyway) that it was asked for too near to the C# 7 release and would have taken too long to implement, so wasn't up for consideration. As it would now be a breaking change, it's not something that will ever happen.

Please see the "Allow Deconstruct and implicit operator to both support deconstruction and conversion to tuple types" roslyn repo issue for the actual discussion on the matter.

Caryl answered 12/5, 2017 at 7:39 Comment(0)
B
6

Here's one way to achieve what you're trying to do: in your code sample, instead of (string a, string b) f = c;, use (string a, string b) f = (_, _) = c;.

You could also write a user-defined conversion from your type to the tuple type you need.

Beaner answered 3/7, 2017 at 5:55 Comment(1)
I really admire the trick here! I'd never want to see that in production code, but it's neat :)Anthracosis

© 2022 - 2024 — McMap. All rights reserved.