1. 元组
1.1 什么是元组
元组是具有固定长度的有序数值序列。 元组的每个元素都有一个类型和一个可选名称。
举个例子帮你理解 (顺便帮你回忆一下上一篇的内容):
var pt = (X: 1, Y: 2);
var slope = (double)pt.Y / (double)pt.X;
Console.WriteLine($"point is {pt} and slope is {slope}.");
1.2 如何修改
你还可以重新分配元组中的任何成员
var pt = (X: 1, Y: 2);
pt.X = pt.X + 5;
Console.WriteLine($"point is {pt}.");
1.3 元组类型
元组是结构类型。 换言之,元组类型没有 string
或 int
这样的名称。
元组类型由成员数(称为 arity)和这些成员的类型定义。 成员姓名是为了方便起见。
即使成员具有不同的名称,也可以将元组赋值给具有相同算式和类型的元组。
别担心,我来举例让你更好理解:
var pt = (X: 1, Y: 2);
var subscript = (A: 0, B: 0);
subscript = pt;
Console.WriteLine($"point is {pt}.");
Console.WriteLine($"subscript is {subscript}.");
也就是:首先,变量 subscript
有两个成员,两者均为整数。
其次,subscript
和 pt
表示同一元组类型的实例,因其都是包含 2 个 int
成员的元组
即使两者的成员具有不同的名称,所以可以进行赋值
1.4 创建元组
元组很容易创建:可以声明多个用括号括起来的成员。
举几个例子:
var point = (10, 20); // 无名元组
var namedData = (Name: "Bo luo", Temp: 2, Wind: 10);
var person = (FirstName: "", LastName: "");
var order = (Product: "cat", style: "cute", quantity: 500, Price: 0.10m);
1.5 with
表达式
还可以使用 with
表达式来创建一个新元组(pt2就是新元组)
它是原始元组的修改副本。(pt2就是pt的修副本,我们把Y的值改成了10)
var pt = (X: 1, Y: 2);
var pt2 = pt with { Y = 10 };
Console.WriteLine($"point is {pt}.");
Console.WriteLine($"pt2 is {pt2}.");
1.6 创建记录类型
虽然元组很容易创建,但功能有限。
官方说法:元组类型没有名称,因此无法向值集传达含义。 元组类型不能添加行为。
(可能有点晕,不过我会尽量的解释,希望能帮到你)
我们用这个元组举例:var catFood = (猫粮: 2, 冻干: 3);
元组就像是你写给自己的纸条(猫粮2个,冻干3个)
补充一个概念:函数(Function)- 目前知道有它即可
函数就类似于你教其他人喂猫的步骤
void 喂猫步骤(int 食物1的数量,int 食物2的数量)
{
1.喂猫粮
2.喂冻干
3.陪“菠萝”玩
}
那么此时会有一个问题,传递时只能传递数值部分,名称部分是不知道的
你传递给其他人的步骤是:喂猫步骤(2,3);
其他人拿到数据后,完全分不清到底是两个猫粮还是两个冻干
这就是元组的功能有限,此时可以通过record来解决问题!
方式 | 过程 | 结果 |
---|---|---|
寄实物 | 箱子装上2猫粮+3冻干(贵,慢) | ✅ 收到直接认识 |
寄纸条"2,3" | 只写两个数字 | ❌ 不知道是什么 |
寄纸条(元组) "(猫粮:2,冻干:3)" | 名字写在纸条上 | ❌ 但运输公司会撕掉名字标签 |
寄正式购物清单 (record) | 列出: • 猫粮:2 • 冻干:3 | ✅ 保留名称和数量 |
record
声明是 foodBook
类型的这一行代码,它将 猫粮数量
和 冻干数量 值存储。
我们为代表 猫粮数量 和 冻干数量 值的元组添加了格式
让它成为一个 record
,定义了一个命名的类型(创建专属名称“foodBook”)
只要使用该类型,就必须使用 foodBook
名称
// 创建食材本(record)
record foodBook(int 猫粮数量, int 冻干数量);
void 喂猫步骤(foodBook 今天食材)
{
Console.WriteLine($"用{今天食材.猫粮数量}个猫粮");
Console.WriteLine($"打{今天食材.冻干数量}个冻干");
}
//2对应了猫粮,3对应了冻干
var 今日任务 = new foodBook(2, 3);
喂猫步骤(今日任务);
当只需要数字时用元组,当需要表达"这是什么"时一定用record!(希望这个例子有帮助)
关于添加行为:
记录成员可以是一个函数(比如下例中的打印总数量)
也可以是多个数据元素(之后学习,目前知道即可)
一个类型的成员在类型声明中,位于 {
和 }
字符之间。
// 创建食材本(record)
record foodBook(int 猫粮数量, int 冻干数量)
{
void 打印总数量()
{
Console.WriteLine($"总数量{猫粮数量 + 冻干数量}");//结果是5
}
}
//2对应了猫粮,3对应了冻干
var 今日任务 = new foodBook(2, 3);
//调用食材本中的方法
今日任务.打印总数量();
补充一点知识,防止混淆:
函数 vs 方法
关于函数(独立存在):
// 独立函数(不属于任何类型)
void 打印总数(int 猫粮, int 冻干)
{
Console.WriteLine($"总数量{猫粮 + 冻干}");
}
// 调用方式(直接调用)
打印总数(2, 3); // 输出:总数量5
方法(定义在记录内部 → 属于 foodBook
类型):
record foodBook(int 猫粮数量, int 冻干数量)
{
// 记录内部的方法
public void 打印总数量()
{
Console.WriteLine($"总数量{猫粮数量 + 冻干数量}");
}
}
// 调用方式(通过对象调用)
var 今日任务 = new foodBook(2, 3);
今日任务.打印总数量(); // 输出:总数量5
记住这个简单规则:
- 看到
对象.名字()
→ 这是方法 - 看到
名字()
→ 这是函数
2. 结构、类和接口类型
record
类型是 record class
的简写: 包含额外行为的 class
类型。
也可以修改,使其成为 record struct
:
接下来我会换一种例子分别解释:
2.1 record struct
(值类型记录)
record struct Point(int X, int Y);
// 使用
var p1 = new Point(1, 2);
var p2 = p1; // 值复制:p2 是独立副本
p2.X = 3; // 不影响 p1
Console.WriteLine(p1); // 输出:Point { X = 1, Y = 2 }
Console.WriteLine(p1 == p2); // 判断是否相等,结果:false(基于值比较)
2.2 record class
(引用类型记录)
record class Point(int X, int Y);
// 使用
var p1 = new Point(1, 2);
var p2 = p1; // 复制引用(指向同一对象)
// p2.X = 3; ❌ 错误!默认不可变
var p3 = new Point(1, 2);
Console.WriteLine(p1 == p2); // true(同一实例)
Console.WriteLine(p1 == p3); // true(值相同,与引用无关)
简单决策法:
- 需要内存高效+频繁复制 → 选
record struct
- 需要共享数据+不可变性 → 选
record class
C# 中的所有命名类型都是 class
或 struct
类型。 class
是引用类型。 struct
是一个值类型。
值类型的变量在内存中内联存储实例的内容。
换言之,record struct Point
存储了两个整数:X
和 Y
引用类型的变量存储指向实例存储空间的引用或指针。
换言之,record class Point
存储的是对内存块的引用,该内存块保存了 X
和 Y
的值。
学到了这里,咱俩真棒,记得按时吃饭(北京今天下大雨,上班是牛马,下班是河马)
【本篇结束,新的知识会不定时补充】