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?
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;
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).
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"
};
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With