在C#中,变量声明"var (name, categoryId) = object"被称为什么?

8

我正在使用 .NET 4.8 并声明一个带有解构方法的记录:

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);
}

然后我使用以下代码进行编译,它可以正常工作:

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

虽然这段代码不起作用,但会生成错误:

"错误 CS0029:无法将类型 'ConsoleApp4.Product' 隐式转换为 'System.Tuple<string, int>'"):

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

声明 var (name, categoryId) 不够清晰。这是什么?
这个变量的类型是什么?在规范中,这种结构被称为什么?
这是自动生成的类型吗,其中 namecategoryId 是其属性吗?

6
那被称为“解构”(Deconstruction)。顺带一提,在一个record中,你无需编写Deconstruct方法,因为它会被自动生成,除非你需要自定义它。你的record可以重写为 public record Product(string Name, int CategoryId); ,并且它将意味着完全相同的事情。 - Aluan Haddad
var 可以自动推断类型。它并不指向任何特定的类型。 - Beingnin
如果您不喜欢 var (name, categoryId) = product;,那么您也可以写成 (string name, int categoryId) = product;。这样您就可以直接看到变量的类型了。 - SomeBody
我以为记录类型是C# 9.0的新功能,而且c# 9.0仅支持.NET 5。我错过了什么吗?您是如何使用.NET 4.8编译此内容的? - Igor
@Igor:我也很惊讶。我已经双重检查了项目目标框架,它是4.8,但编译器同时不知道什么是“init”。我早先尝试在我的机器上安装.NET 5,它已经安装完成,但是.NET 5在项目选项中不可用。 - usr2020
2个回答

4

请注意语法

var (name, categoryId) = product;

这是一种析构,而不是分配给元组的操作。

来自官方文档

从 C# 7.0 开始,您可以在单个析构操作中检索元组的多个元素或检索对象中的多个字段、属性和计算值。当你析构一个元组时,你将其元素分配给单独的变量。当你析构一个对象时,你将选定的值分配给单独的变量。

暂且不考虑 Deconstruct,任何元组都可以被析构成单独的变量,只要提供足够的变量(或抛弃使用的变量,_)来容纳该元组即可。

例如:

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

"Hello"分配给name,并将123分配给categoryId

以下所有内容都是等效的

(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);

同样地,通过为您自己的类/记录提供适当的Deconstruct重载或扩展方法,您可以将多个变量分配给匹配的Deconstruct方法的out参数:

var (name, categoryId) = Product; 

这告诉编译器为Product查找一个合适的Deconstruct重载。因为你对所有已析构的变量使用了var类型推断,所以析构函数必须有两个参数(任意类型,将被推断)。

这里还有一些其他微妙之处。

首先,如你所见,你可以为Product记录声明许多不同的析构函数,只要这些析构函数的签名不同即可。

值元组语法

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

仅仅是一个方便的简写

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

当您执行以下赋值操作时:
 var (name, categoryId) = product;
  1. 在这种情况下,由于您使用了var类型推断,因此为Product找到了适当的解构重载,该解构必须具有2个参数(但可以是任何类型)。

  2. 然后,将out变量分配给您的解构变量,您也将它们命名为string nameint categoryId

尽管您无法直接将其解构为System.ValueTupleSystem.Tuple,但您可以从两者中进行解构。

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

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

Deconstruction的主要用途之一是在模式匹配期间进行简写符号,您可以快速推理类型和属性:
例如,而不是
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"
};

你可以将其拆解和/或丢弃,以满足需要。
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"
};

1

我相信这就是所谓的解构(或者在Python中叫做拆包)。一般来说,这只是C# 7.0中推出的一个功能,可以节省您的时间,不需要声明所有元组,然后逐个访问其项。欲了解更多信息,请查看此处


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接