本教程教你如何在 C# 中创建类型。 你将编写少量的代码,然后编译并运行这些代码。 本教程包含一系列探索 C# 中不同类型的课程。 这些课程介绍了 C# 语言的基础知识。
小提示
当代码片段块包含“运行”按钮时,该按钮会打开交互式窗口,或替换交互式窗口中的现有代码。 如果代码段不包含“运行”按钮,则可以复制代码并将其添加到当前交互式窗口中。
前面的教程使用的是文本和数字。 字符串和数字是简单类型:它们各自存储一个值。 随着程序规模的扩大,需要使用更为复杂的数据结构。 在需要具有更多字段、属性或行为的数据结构时,C# 提供了可以定义的不同类型。 现在开始探讨这些类型。
元组
元组是具有固定长度的有序数值序列。 元组的每个元素都有一个类型和一个可选名称。 以下代码声明了一个表示二维点的元组。 选择“运行”按钮,将以下代码粘贴到交互式窗口并运行。
var pt = (X: 1, Y: 2);
var slope = (double)pt.Y / (double)pt.X;
Console.WriteLine($"A line from the origin to the point {pt} has a slope of {slope}.");
小提示
在探索 C#(或任何编程语言)的过程中,可能会在编写代码时犯错。 编译器会发现这些错误并向你报告。 如果输出中包含错误消息,请仔细查看示例代码和交互式窗口中的代码,以了解应如何纠正。 该练习有助于学习 C# 代码的结构。
可以重新分配元组中的任何成员。 在交互式窗口中现有代码后添加以下代码。 再次按“运行”以查看结果。
pt.X = pt.X + 5;
Console.WriteLine($"The point is now at {pt}.");
还可以使用 with
表达式来创建一个新元组,它是原始元组的修改副本。 在交互式窗口中的代码后添加以下代码,然后按“运行”查看结果:
var pt2 = pt with { Y = 10 };
Console.WriteLine($"The point 'pt2' is at {pt2}.");
元组 pt2
包含 pt
的 X
值 (6),而 pt2.Y
是 10。
元组是结构类型。 换言之,元组类型没有 string
或 int
这样的名称。 元组类型由成员数(称为 arity)和这些成员的类型定义。 成员姓名是为了方便起见。 即使成员具有不同的名称,也可以将元组赋值给具有相同算式和类型的元组。 可以在交互式窗口中已编写的代码后添加以下代码并尝试:
var subscript = (A: 0, B: 0);
subscript = pt;
Console.WriteLine(subscript);
变量 subscript
有两个成员,两者均为整数。 subscript
和 pt
表示同一元组类型的实例:包含 2 个 int
成员的元组。
元组很容易创建:可以声明多个用括号括起来的成员。 以下所有元组都声明了不同的元数和成员类型。 添加以下代码以创建新的元组类型:
var namedData = (Name: "Morning observation", Temp: 17, Wind: 4);
var person = (FirstName: "", LastName: "");
var order = (Product: "guitar picks", style: "triangle", quantity: 500, UnitPrice: 0.10m);
元组很容易创建,但功能有限。 元组类型没有名称,因此无法向值集传达含义。 元组类型不能添加行为。 当类型定义了行为时,C# 还可以创建其他类型。
创建记录类型
如果需要在同一结构中使用多个值,元组是个不错的选择。 它们很轻量,并且可以在使用时进行声明。 在程序运行过程中,可能会发现整个代码都在使用相同的元组类型。 如果应用确实在二维图形空间中运行,那么表示点的元组可能很常见。 找到这些值后,就可以声明一个 record
类型来存储这些值,并提供更多的功能。 下面的代码示例使用 Main
方法表示程序的入口点。 这样,就可以在代码的入口点之前声明一个 record
类型。 按以下代码上的“运行”按钮,用以下代码替换现有样本。
警告
不要复制和粘贴。 要运行以下示例,必须重置交互式窗口。 如果出错,窗口就会挂起,需要刷新页面才能继续。
以下代码声明并使用 record
类型来表示 Point
,然后在 Main
方法中使用该 Point
结构:
public record Point(int X, int Y);
public static void Main()
{
Point pt = new Point(1, 1);
var pt2 = pt with { Y = 10 };
Console.WriteLine($"The two points are {pt} and {pt2}");
}
record
声明是 Point
类型的一行代码,它将 X
和 Y
值存储在只读属性中。 只要使用该类型,就必须使用 Point
名称。 正确命名的类型(如 Point
)会提供有关如何使用该类型的信息。 Main
方法介绍了如何使用 with
表达式来创建一个新点,该点是现有点的修改副本。 pt2 = pt with { Y = 10 }
行中写道:“除了 Y
被赋值为 10 之外,pt2
的值与 pt
相同”。可以在一个 with
表达式中添加任意数量要更改的属性。
与所有 C# 语句一样,前面的 record
声明是一行以 ;
结尾的代码。 可以通过声明成员为 record
类型添加行为。 记录成员可以是一个函数,也可以是多个数据元素。 一个类型的成员在类型声明中,位于 {
和 }
字符之间。 用以下代码替换你所做的记录声明:
public record Point(int X, int Y)
{
public double Slope() => (double)Y / (double)X;
}
然后,在 Main
方法中包含 with
表达式的行之后添加以下代码:
double slope = pt.Slope();
Console.WriteLine($"The slope of {pt} is {slope}");
你为代表 X
和 Y
值的元组添加了格式。 你让它成为一个 record
,定义了一个命名的类型,并包含了一个计算斜率的成员。 record
类型是 record class
的简写: 包含额外行为的 class
类型。 也可以修改 Point
类型,使其成为 record struct
:
public record struct Point(int X, int Y)
{
public double Slope() => (double) Y / (double) X;
}
record struct
是一种 struct
类型,包含添加到所有 record
类型中的额外行为。
结构、类和接口类型
C# 中的所有命名类型都是 class
或 struct
类型。 class
是引用类型。 struct
是一个值类型。 值类型的变量在内存中内联存储实例的内容。 换言之,record struct Point
存储了两个整数:X
和 Y
。 引用类型的变量存储指向实例存储空间的引用或指针。 换言之,record class Point
存储的是对内存块的引用,该内存块保存了 X
和 Y
的值。
实际上,这意味着值类型在分配时会被复制,但类实例的副本就是引用的副本。 复制的引用指向同一个点的实例,X
和 Y
的存储空间相同。
record
修改器指示编译器为你编写多个成员。 可以在基础知识部分的记录类型一文中了解更多信息。
在声明一个 record
类型时,你就声明了自己的类型应该使用一组默认行为来进行等价比较、赋值和复制该类型的实例。 当存储相关数据是你的类型的主要责任时,记录是最好的选择。 在添加更多行为时,请考虑使用 struct
或 class
类型,而不使用 record
修饰符。
当需要更复杂的行为时,可以使用 struct
类型作为值类型,但其主要职责是存储值。 可以使用 class
类型来使用面向对象的习语,如封装、继承和多态性。
还可以定义 interface
类型来声明不同类型必须实现的行为合约。 struct
和 class
类型都可以实现接口。
你通常会在大型程序和程序库中使用所有这些类型。 在安装 .NET SDK 后,可以使用基础知识部分的类教程来探索这些类型。
你已完成“在 C# 中创建类型”交互式教程。 可选择分支和循环链接开始下一个交互式教程,或者可访问 .NET 站点下载 .NET SDK,在计算机上创建项目,并继续编码。 请通过“后续步骤”部分返回到这些教程。
可以在以下文章中了解有关 C# 中类型的详细信息: