Cannot implicitly convert deconstructable type to Tuple

Viewed 449

I have two structs (Point and Size) with Deconstruct() method. Both return (int, int) tuple.

I'm trying to deconstruct them to (int, int) tuple variable in switch without success. The compiler gives me the error:

Cannot implicitly convert type 'Size' to '(int, int)'.

How can I deconstruct these both structs to one tuple to have the ability to use it in the second switch expression?

using System;

class Program
{
    static string DescribeSize<TDeconstructable>(TDeconstructable deconstructableStruct)
    where TDeconstructable : struct
    {
        (int, int) xy;
        switch (deconstructableStruct)
        {
            case Size size:
                xy = size; //CS0029 error: Cannot implicitly convert type 'Size' to '(int, int)'
                break;
            case Point point:
                xy = point; //CS0029 error: Cannot implicitly convert type 'Point' to '(int, int)'
                break;
            default:
                xy = (0, 0);
                break;
        }

        return xy switch
        {
            (0, 0) => "Empty",
            (0, _) => "Extremely narrow",
            (_, 0) => "Extremely wide",
            _ => "Normal"
        };
    }

    static void Main(string[] args)
    {
        var size = new Size(4, 0);
        var point = new Point(0, 7);
        Console.WriteLine(DescribeSize(size));
        Console.WriteLine(DescribeSize(point));
    }
}

public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    public void Deconstruct(out int x, out int y)
    {
        x = X;
        y = Y;
    }
}

public readonly struct Size
{
    public int W { get; }
    public int H { get; }

    public Size(int w, int h)
    {
        W = w;
        H = h;
    }

    public void Deconstruct(out int w, out int h)
    {
        w = W;
        h = H;
    }
}
4 Answers

You can add this helper method inside the structs to return a ValueTuple if needed.

public ValueTuple<int, int> GetValueTuple()
{
    return (X, Y);
}

And then invoke the method inside switch cases like this.

case Size size:
     xy = size.GetValueTuple();
     break;
case Point point:
     xy = point.GetValueTuple();
     break;

There's no implicit conversion to ValueTuple, and your assignment is not invoking the deconstruction. You're essentially attempting to do the following which is not legal:

xy = (ValueTuple<int, int>)size;

You need two variables, not one. This is made more obvious if you consider that deconstruction is just compiler trickery/syntactic sugar for invoking the Deconstruct method. If you were going to call it manually, you'd need to pass two variables as out parameters, not one.

int x, y;
size.Deconstruct(out x, out y);

There's not an overload that takes a single tuple. How would that work? There's only a single variable. I suppose you might be thinking that the compiler could do something akin to:

size.Deconstruct(out xy.Item1, out xy.Item2);

Unfortunately it doesn't. For your case, you'll need to declare the variables seperately (not as a ValueTuple) and then use deconstruction and tuple-syntax to assign to them. If you wanted, you could move the assignment from the default case out of the switch to give the declaration of the two variables a more tuple-y feel:

var (x, y) = (0, 0); // two variables 
switch (deconstructableStruct)
{
    case Size size:
        (x, y) = size;
        break;
    case Point point:
        (x, y) = point;
        break;
}

You can still switch on the values later, you just need to use tuple syntax:

return (x, y) switch {
    (0, 0) => "Empty", 
    (0, _) => "Extremely narrow", 
    (_, 0) => "Extremely wide",
     _ => "Normal" 
};

See this answer for a good alternative: writing your own implicit conversion operator if you really need a variable that is of a tuple-type on it own (and you are both able to and don't mind modifying the Size and Point types to add this behavior).

Your code example is too complicated. The issue has nothing to do with the generic method nor the switch. The following is sufficient to reproduce the problem:

var size = new Size(4, 0);
(int, int) xy;

xy = size;

This answer explains specifically why this syntax cannot work. Of course, that really just boils down to a syntax error. You simply haven't written the code correctly. If you don't really need for the local variable to be a tuple type, that answer provides a good alternative.

That said, it is also possible to achieve what you want using a tuple type variable, if you're able to change the Point and Size types. You can simply provide the implicit conversion that the error message explains does not currently exist. For example, you could add the following to the Size type:

public static implicit operator (int x, int y)(Size size)
{
    return (size.W, size.H);
}

Then the assignment would work just as you'd like, everywhere, not just in the switch statement.

You could make a tuple from the parts of each type:

    case Size size:
        xy = (size.Length, size.Height);
        break;
    case Point point:
        xy = (point.X, point. Y);
        break;

Related