Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the variable declaration "var (name, categoryId) = object" called in C#?

Tags:

c#

I'm using .NET 4.8 and declare a record with a Deconstructor:

public record Product
{
    public string Name { get;  }
    public int CategoryId { get;  }

    public Product(string name, int categoryId)
      => (Name, CategoryId) = (name, categoryId);

    public void Deconstruct(out string name, out int categoryId)
      => (name, categoryId) = (Name, CategoryId);
}

Then I use the following code which is compiled and it works fine :

var product = new Product("VideoGame", 1);
var (name, categoryId) = product;
string s = name;
int i = categoryId;

While this code doesn't work an error is generated:

"Error CS0029 Cannot implicitly convert type 'ConsoleApp4.Product' to 'System.Tuple<string, int>'"):

var product = new Product("VideoGame", 1);
Tuple<string, int> t = product;
string s = t.Item1;
int i = t.Item2;

The declaration var (name, categoryId) is not clear. What is it?
What is the type of this variable? How is this construction called in the specification?
Is that an auto-generated type behind the scene and the name and the categoryId are its properties?

like image 578
usr2020 Avatar asked Oct 05 '20 04:10

usr2020


2 Answers

Note that the syntax

var (name, categoryId) = product;

is a deconstruction - it is NOT an assignment to a tuple.

From the docs

Starting with C# 7.0, you can retrieve multiple elements from a tuple or retrieve multiple field, property, and computed values from an object in a single deconstruct operation. When you deconstruct a tuple, you assign its elements to individual variables. When you deconstruct an object, you assign selected values to individual variables.

Ignoring Deconstruct for a moment, any tuple can be deconstructed into individual variables, provided that sufficient variables (or the discard, _) be provided to accomodate the tuple.

e.g.

(string name, int categoryId) = ("Hello", 123);

Assigns "Hello" to name, and 123 to categoryId

All of the below are equivalent

(string name, int categoryId) = ("Hello", 123); // Types of tuple match position vars
(var name, var categoryId) = ("Hello", 123); // Type variable types are inferred
var (name, categoryId) = ("Hello", 123);

Similarly, by providing suitable Deconstruct overloads or extension methods for your own classes / records, you can assign multiple variables to the out parameters of the matched Deconstruct method:

var (name, categoryId) = Product; 

which tells the compiler to look for an appropriate Deconstruct overload for Product. In this case, because you're using var type inference for all the deconstructed, the deconstructor must have 2 parameters (of any type, which will be inferred).

There are some other nuances happening here.

Firstly, as you've seen, you can declare many different Deconstructions for your Product record, as long as the signatures of the deconstructions differ.

The (value) tuple syntax

public void Deconstruct(out string name, out int categoryId)
    => (name, categoryId) = (Name, CategoryId);

is just a convenient short hand for

public void Deconstruct(out string name, out int categoryId)
{
    name = Name;
    categoryId = CategoryId;
}

When you make the following assignment:

 var (name, categoryId) = product;
  1. An appropriate deconstruct overload is located for Product, in this case, because you're using var type inference, the deconstructor must have 2 parameters (but any type).

  2. The out variables are then assigned to your deconstruct variables, which you've also named string name and int categoryId.

Although you can't deconstruct directly INTO a System.ValueTuple or System.Tuple, you can deconstruct FROM both

var (name, categoryId) = Tuple.Create("Hello", 123); // Old Heap tuples

var (name, categoryId) = ("Hello", 123); // Newer value tuples

One of the major uses of Deconstruction is for short hand notation during Pattern matching, where you can quickly reason over the type and properties:

e.g. instead of

var result = product switch
{
  Product x when x.CategoryId == 3 => "You've got a category 3 product",
  Product x when string.IsNullOrWhiteSpace(x.Name) => "That product looks broken",
  _ => "Just a standard product"
};

You can instead deconstruct and / or discard as necessary:

var result2 = product switch
{
  var (_, cat) when cat == 3 => "You've got a category 3 product",
  var (str, _) when string.IsNullOrWhiteSpace(str) => "That product looks broken",
  _ => "Just a standard product"
};
like image 197
StuartLC Avatar answered Nov 15 '22 01:11

StuartLC


I believe this is called just like that deconstruction (or unpackage like in python). In general this is only a feature that came out in C# 7.0 just to save you time by not declaring all the tuple and then access its items one by one. For more information look here.

like image 31
Jorge Morgado Avatar answered Nov 14 '22 23:11

Jorge Morgado