Tip
本文是已了解至少一种编程语言并正在学习 C# 的开发人员的 “基础知识 ”部分的一部分。 如果模式不熟悉,请从 模式匹配概述 开始。
解构将值的各个部分(其组件)分配给单个操作中的多个变量。 元组的分量是其元素,可按位置访问。 另一种类型可以通过定义 Deconstruct 方法来暴露组件。
位置记录类型(将其属性声明为类似构造函数的参数)会自动获得一个 Deconstruct 方法。
解构元组
假设某个方法返回包含城市数据的元组。 可以逐个读取每个组件:
var cityData = QueryCityData("New York City");
var city = cityData.City;
var population = cityData.Population;
var area = cityData.Area;
解构赋值可在一个步骤中将这些组成部分分别赋值:
(string city, int population, double area) = QueryCityData("New York City");
还可以让 C# 推断变量类型:
var (city, population, area) = QueryCityData("New York City");
解构可以在一个赋值中混合现有变量、新声明的变量和丢弃项:
(city, var population, _) = QueryCityData("New York City");
选择使代码更易于阅读的形式。 括号前的单个 var 值通常是最清晰的推断形式。 还可以在括号内混合显式类型和 var,但这种形式通常更难扫描。 如果只需要一些值,请使用丢弃符而不是省略位置。
使用舍弃项忽略不需要的值
每个生成的值都必须与赋值左侧的位置对齐。 如果不需要一个或多个位置,请使用 _ 作为丢弃符:
var (_, _, population1960, _, population2010) = QueryPopulationDataForYears(
"New York City", 1960, 2010);
在这里,元组返回城市名称、两年和两个人口值。 解构仅保留总体数据值,因为计算仅使用这些组件。
分解用户定义类型
类、结构或接口可以通过声明 Deconstruct 方法来支持解构。 每个组成部分都会成为一个 out 参数,这样该方法就可以将值赋回调用方变量而不返回它。 由于每个组件都通过参数 out 返回,因此该方法本身返回 void:
public void Deconstruct(out string firstName, out string middleName, out string lastName)
{
firstName = FirstName;
middleName = MiddleName;
lastName = LastName;
}
然后可以直接解构实例:
var (firstName, middleName, lastName) = passenger;
类型可以提供多个 重载,具有不同的 out(方法声明的 Deconstruct 参数数),因此调用方可以选择要检索的组件数:
public void Deconstruct(out string firstName, out string lastName)
{
firstName = FirstName;
lastName = LastName;
}
public void Deconstruct(out string firstName, out string middleName, out string lastName)
{
firstName = FirstName;
middleName = MiddleName;
lastName = LastName;
}
public void Deconstruct(out string firstName, out string lastName, out string city, out string state)
{
firstName = FirstName;
lastName = LastName;
city = City;
state = State;
}
具有相同数量的 out 参数的两个重载存在歧义。 编译器会对歧义调用报告错误,因此,应通过元数区分重载,而不仅仅是通过参数类型。
丢弃也适用于用户定义的解构。 有关一般弃牌的详细信息,请参阅 “弃牌”和弃牌模式:
var (firstName, _, city, _) = passenger;
解构记录
位置record或record struct类型将其属性声明为类型声明本身的参数,类似于构造函数。 编译器会为你生成一个 Deconstruct 方法,其中包含 out 与这些位置参数匹配的参数:
var (city, highTempC, lowTempC) = forecast;
只有位置参数参与生成的析构。 不会自动添加记录上其他位置声明的其他属性。
解构你不拥有的类型
如果无法修改类型,仍可以通过编写扩展方法(一种静态方法)来支持解构,该方法会向你不拥有的类型添加 Deconstruct 方法,就好像它是该类型的成员一样。 添加该方法后,任何 Uri 值都可以使用解构语法:
static class UriExtensions
{
public static void Deconstruct(this Uri uri, out string scheme, out string host, out int port)
{
scheme = uri.Scheme;
host = uri.Host;
port = uri.Port;
}
}
此处同样适用相同的歧义规则:两个参数个数相同的扩展 Deconstruct 方法会产生歧义。 实例 Deconstruct 方法和参数个数相同的扩展方法之间也可能出现歧义。 在任一情况下,编译器都报告了不明确调用的错误。
系统类型的内置解构
某些系统类型已经定义了一个 Deconstruct 方法,使用与您自己的类型相同的机制。 例如,System.Collections.Generic.KeyValuePair<TKey,TValue> 支持解构,这使得字典迭代简洁:
foreach (var (repo, commitCount) in repoCommitCounts)
{
Console.WriteLine($"{repo} had {commitCount:N0} commits in this snapshot.");
}
析构和模式匹配
Deconstruct 方法还启用该类型的 位置模式。 位置模式在一步中测试并解构一个值,使用与解构相同的括号语法:person is ("Alice", 30) 匹配其解构后的组件等于这些值的 Person。 这不同于 属性模式,该模式直接测试命名属性,例如 person is { Name: "Alice", Age: 30 }。 对于对象形状,属性模式通常更清晰,因为成员名称解释测试。 当顺序已包含含义时,位置模式最强,例如元组或其他较小的有序值。