CodeGym /课程 /C# SELF /元素命名和元组解构

元素命名和元组解构

C# SELF
第 11 级 , 课程 2
可用

1. 元组命名:为解构提供上下文

在上一节课我们已经见过命名元组元素了,这样你写代码的时候不用再用那些没意义的Item1Item2,而是能用有意义的名字(比如person.Name)。给元组元素命名特别重要,尤其是当它们作为方法、参数或者属性的返回值时。命名其实就是为后面方便解构打基础,这节课我们就要详细聊聊这个。

再提醒一下,名字可以在初始化元组时直接写字面量,也可以在方法、属性或者字段的返回类型签名里写。

方法例子: 方法返回值里的命名元组元素


public static (int 年龄, string 名字) GetPetInfo()
{
    return (年龄: 5, 名字: "巴尔西克");
}

// 使用方法:
var info = GetPetInfo();
Console.WriteLine($"{info.名字} — {info.年龄} 岁");

字段/属性例子: 字段/属性里的命名元组元素


public (int 宽度, int 高度) ImageSize = (1024, 768);

记住:如果没显式写名字,元素还是能通过Item1Item2这样访问。这样代码就没那么好读了,特别是元组用得多的时候。所以建议只要能表达清楚意思,就给元素起个有意义的名字。

2. 元组解构

什么是解构?

解构就是把元组“拆开”成单独的变量,这样用起来更方便。比如你有个元组(年龄: 5, 名字: "巴尔西克"),可以直接拿到两个变量:agename

类比:想象一下,元组就像一个有标签的盒子。解构就是你把盒子里的东西直接摊在桌子上,各归各位。

解构语法


var pet = (年龄: 5, 名字: "巴尔西克");
var (age, name) = pet;
Console.WriteLine($"{name} — {age} 岁");
元组解构成变量

现在我们有了两个变量:agename。它们的值就是从元组里来的。左边的名字(agename)不用和元组里的名字一样,这就是新的本地变量

解构函数返回值

元组经常用来从函数里返回多个值,这时候解构就特别爽:


public static (double 最小值, double 最大值) GetMinMax(int[] 数据)
{
    int 最小值 = 数据.Min();
    int 最大值 = 数据.Max();
    return (最小值, 最大值);
}

var numbers = new[] { 1, 2, 3, 4, 5 };
var (minValue, maxValue) = GetMinMax(numbers);
Console.WriteLine($"最小值: {minValue}, 最大值: {maxValue}");
解构函数返回值

注意,解构时变量名可以随便起,怎么方便怎么来。

var解构

var (a, b) = (10, 20); // int a = 10, b = 20

解构和 discard _

有时候元组里的所有元素你都不需要,可以用_(discard)来忽略。 discard(_)就是告诉编译器“我知道这里有个元素,但我不关心,不用给我变量”。

这样你解构元组的时候,可以在不需要的地方留个“空”,又简洁又方便!


var pet = (年龄: 5, 名字: "巴尔西克", 开心: true);
var (age, _, isHappy) = pet; // 只要age和isHappy,名字直接忽略
用 discard _ 解构

有意思的是:discard经常用来避免命名空间里出现一堆没用的变量。

foreach循环里解构

C#里你可以直接在foreach里解构元组数组:


var pets = new (string 名字, int 年龄)[]
{
    ("巴尔西克", 5),
    ("穆西亚", 3),
    ("约翰尼", 7)
};

foreach (var (name, age) in pets)
{
    Console.WriteLine($"{name} — {age} 岁");
}
foreach里解构元组

3. 元素名和类型是怎么工作的

命名元素的行为

元组元素的名字其实就是“语法糖”,也就是让人看着舒服点。编译的时候名字会变成内部的Item1Item2啥的,但IDE和编译器会保留名字,这样你用起来更方便。

对类型兼容性和类型转换的影响

两个元素数量和类型一样的元组,不管名字是不是一样,对编译器来说就是同一个类型。元素名不影响类型判断:


var t1 = (X: 42, Y: 13);
var t2 = (A: 42, B: 13);
t1 = t2; // OK
Console.WriteLine(t1.X); // 42
不同元素名的元组类型兼容

不过在用表达式和IntelliSense提示时,还是会用左边(赋值目标)的名字,不会用右边的。

隐式和显式命名

我们之前讲过,可以只给部分元素命名,也可以全都不命名——那就只能用Item1啥的来访问了。


var point = (X: 10, 20); // X和Item2
Console.WriteLine(point.X);     // 10
Console.WriteLine(point.Item2); // 20
部分命名的元组

建议:如果元组有两个以上的元素,或者值的意思不明显,最好都命名一下。

4. 用名字和解构时常见的坑和注意点

错误1:“名字漂移”——赋值不同元组时名字变化
如果你先声明了一个有名字的元组,然后又给它赋了个没名字(或者名字不一样)的元组,IntelliSense里还是会显示最开始的名字。比如:


var original = (X: 1, Y: 2);
var alias = original;            // alias.X == 1, alias.Y == 2

original = (10, 20);             // 没有名字的元组
Console.WriteLine(alias.X);      // 还能用,alias还是保留原来的名字

错误2:元素名重复。
不能给两个元素起一样的名字——编译器会报“Duplicate tuple element name”:


var badTuple = (A: 1, A: 2);     // 错误 CS8122: Duplicate tuple element name 'A'

错误3:解构时元素数量不对。
解构时变量数量必须和元组元素数量完全一样。不然就会报错:


var pet = (年龄: 5, 名字: "巴尔西克");
var (age, name, mood) = pet;     // 错误 CS8124: Tuple must contain exactly 3 elements

错误4:错误使用 discard _
_忽略元素时,每个位置都要单独写,不能“合并”成一个。比如你想用一个_同时跳过两个元素,会报错:


var data = (1, 2, 3);
var (_, x, _) = data;            // 正确:跳过第一个和第三个元素
var (_, _) = data;               // 错误 CS8124: Tuple must contain exactly 2 elements

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION