Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# deconstruction and overloads

Tags:

c#

c#-7.0

While investigating the new features in C# 7.x, I created the following class:

using System;
namespace ValueTuples
{
    public class Person
    {
        public string Name { get; }
        public DateTime BirthDate { get; }

        public Person(string name, DateTime birthDate)
        {
            Name = name;
            BirthDate = birthDate;
        }

        public void Deconstruct(out string name,
            out int year, out int month, out int day)
        {
            name  = Name;
            year  = BirthDate.Year;
            month = BirthDate.Month;
            day   = BirthDate.Day;
        }

        public void Deconstruct(out string name,
            out int year, out int month, 
            out (int DayNumber, DayOfWeek DayOfWeek) day)
        {
            name = Name;
            year = BirthDate.Year;
            month = BirthDate.Month;
            day.DayNumber = BirthDate.Day;
            day.DayOfWeek = BirthDate.DayOfWeek;
        }
    }
}

And the following test code:

using System;
namespace ValueTuples
{
    class MainClass
    {
        static void Main()
        {
            var dh = new Person("Dennis", new DateTime(1985, 12, 27));
            // DECONSTRUCTION:
            (string name, _, _, (_, DayOfWeek dow)) = dh;
            Console.WriteLine($"{name} was born a {dow}");
        }
    }
}

If the Person class includes only the SECOND Deconstruct overload, the code compiles and runs fine ("Dennis was born a Friday"). But as soon as the first overload is added to Person, the compiler starts complaining, the error message being:

The call is ambiguous between the following methods or properties: 'Person.Deconstruct(out string, out int, out int, out int)' and 'Person.Deconstruct(out string, out int, out int, out (int DayNumber, DayOfWeek DayOfWeek))' (CS0121) (ValueTuples)

I've read the MSDN and GitHub documentation, but it is not clear to me why the compiler cannot determine that the only applicable overload is the second, given the inner tuple pattern on the left side of the assignment. Any clarification will be appreciated.

like image 616
Octavio Avatar asked Nov 09 '17 06:11

Octavio


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is C language?

C is an imperative procedural language supporting structured programming, lexical variable scope, and recursion, with a static type system. It was designed to be compiled to provide low-level access to memory and language constructs that map efficiently to machine instructions, all with minimal runtime support.

What is C full form?

Full form of C is “COMPILE”. One thing which was missing in C language was further added to C++ that is 'the concept of CLASSES'.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.


1 Answers

To understand what's going on, it's important to remember that in the expression:

(string name, _, _, (_, DayOfWeek dow))

the (_, DayOfWeek dow) part is not a tuple. It's a second deconstruct. So the compiler cannot choose between just using your second Deconstruct to satisfy the five parameters (via deconstructing the tuple to the last two parameters), or taking the day parameter from the first one and then attempting to find a Deconstruct on int to satisfy that part.

To see this in action, comment out the second Deconstruct, then add:

static class MyDeconstruct
{
    public static void Deconstruct(this int i, out int dayNumber, out DayOfWeek dayOfWeek) =>
        (dayNumber, dayOfWeek) = (i, (DayOfWeek)i);
}

At that point, the code once again compiles just fine.

Using the same syntax for tuples and deconstructs brings many advantages. As you have discovered though, it has the disadvantage when you mix the two as the compiler has no way of knowing you want (_, DayOfWeek dow) to be a tuple in your deconstruct, when it's valid deconstruct syntax.

However, there still seems to be a severe limitation to the behaviour of compiler to choose which Deconstruct to use, even when it's provided with sufficient type information to resolve the expression. It takes a very simple approach of matching the arity (number of parameters) only. So if two Deconstruct methods exist with the same number of parameters, it can't choose between them. For example,

(string name, _, _, int day) = dh;

ought to work just fine as we have told it the type of the fourth parameter and thus there is now only one Deconstruct that matches. Yet it still complains it can't choose between the two. I've therefore raised an issue with the C# team to see if that can be improved in a future version of the language.

like image 86
David Arno Avatar answered Oct 15 '22 13:10

David Arno