C#类、记录、结构和元组

本文详细介绍了C#中的类型系统,包括值类型如结构和元组,以及引用类型如类和记录。文章强调了按值传递和按引用传递的区别,并通过示例展示了它们在结构和类中的不同行为。此外,还提到了C#9中的新特性——记录类型,以及元组的使用和它们的底层实现。最后,概述了类的一些基本成员,如字段、方法和属性。
摘要由CSDN通过智能技术生成
到目前为止,我们介绍了组成 C#语言的主要模块,包括变量、数据类型和程序流语句,并展示了一些简短但完整的程序,它们只包含顶级语句和一些方法。但是,我们还没有介绍如何把这些元素组合在一起,构成一个更长的程序。要创建更长的程序,关键在于使用.NET的类型,包括类、记录、结构和元组。
  • 创建及使用类型
  • 按值传递和按引用传递
.NET中的类型可分为按值传递和按引用传递的类型。
按值传递的意思是,如果把一个变量赋值给另外一个变量,将复制前者的值。修改新值并不会导致原值发生变化。赋值时会复制变量的内容。在下面的代码示例中,创建了一个结构,使其包含公有字段 A。x1和x2是这种类型的变量。在创建了x1后,把x1赋值给x2。因为结构是值类型,所以x1中的数据将被复制到x2中。修改x2的公有字段的值并不会影响x1。x1变量仍然会显示原来的值,因为值是被复制到x2中的(代码文件 TypesSample/Program.cs):
AStruct x1 = new() { A=1 };
AStruct x2= x1;
x2.A= 2;
Console.WriteLine ($"original didn't change with a struct: {x1.A}"):
//...
public struct AStruct
{
public int A;
}
注意:
通常,不应该创建公有字段,而是应该使用其他成员,例如属性。这里为了方便读者了解.NET类型的主要区别,使用了公有字段。
类的行为则有很大不同。如果修改了y2变量中的公有成员A,则使用引用y1可以读取y2中的新值。按引用传递意味着在赋值后,变量y1和y2引用相同的对象(代码文件TypesSample/Program.cs):
AClass ylo = new(){A=1};
AClass y2 =y1;
y2.A = 2;
Console.WriteLine($"original changed with a class: {yl.A}");
//...
public class AClass
{
  public int A;
}
类型之间的另外一个值得注意的区别在于数据的存储位置。对于引用类型,例如类,数据存储在托管堆上的内存中。变量本身存储在栈上,但它引用堆上的内容。对于值类型,例如结构,数据通常存储在栈上。这一点对于垃圾回收很重要。如果不再使用堆上的对象,垃圾收集器需要清理这些对象而栈上的内存会在方法结束后自动释放,因为此时变量超出了其作用域。
注意:
结构的值通常存储在栈上。但是,对于装箱,即把结构作为对象传递,或者调用了结构的对象方法时,将把结构的数据移动到堆上。装箱会把结构移动到堆上,拆箱会把它移动回到栈上。C#还有一种从不会在堆上存储数据的类型:ref struct。对于ref struct,如果使用了会把数据移动到堆上的操作,就会产生编译错误。
现在来看看C#9中新增的记录类型。使用record关键字可以创建记录。与上例使用class关键字创建引用类型相似,使用rccord关键字也会创建一个引用类型。C#9记录就是一个类。record关键字只不过是一个“语法糖”(syntax sugar),编译器在后台会创建一个类。运行库不需要为其提供功能。不使用这个关键字,也可以创建与编译器生成的代码相同的代码,只不过你自己需要编写多得多的代码行(代码文件 TypesSample/Program.cs):
ARecord z1 = new() ({A=1 ];
ARecord 22 = Z1;
Z2.A = 2;
Console.WriteLine($"original changed with a record: {z1.A}");
//...
public record ARecord
{
    public int A;
}
注意:
rocord 支持类似于结构的值语义,但被实现为一个类。record 可以方便地创建不可变类型,并且其成员在初始化后不能再被改变。
元组是什么情况呢?使用元组时,可以把多个类型组合为一个类型,而不需要创建类、结构或记录。这种类型的表现如何?
在下面的代码片段中,t1是一个元组,将一个数字和一个字符串组合起来。然后把元组t1赋值给变量t2。如果修改t2的值,t1不会改变。原因在于,在为元组使用C#语法时,编译器在后台会使用ValueTuple 类型(这是一个结构)并复制值(代码文件 TypesSample/Program.cs):
var tl = (Number: 1, String: "a");
var t2 = tl;
t2.Number = 2;
t2.String = "b";
Console,WriteLine($"original didn't change with a tuple: {t1.Number} {t1.String}");
注意:
.NET 提供了 Tuple<T>类型和ValueTuple<I>类型。Tuple<T>是更早的类型,作为类实现。在内置的C#元组语法中,使用了ValueTuple。ValueTuple为元组的所有成员包含公有字段。更早的Tuple<T>类型包含公有只读属性,其值不能改变。如今,在应用程序中不需要使用 Tuple<T>类型,因为ValueTuple<T>有更好的内置支持。
在了解了类、结构、记录和元组之间的主要区别后,我们接下来深入探讨类,包括类的成员。类的大部分成员也适用于记录和结构。在介绍完类的成员后,我们将讨论记录和结构的区别。
成员
说明
字段
字段是类的数据成员,它是类型的一个变量,该类型是类的一个成员
常量
常量与类关联在一起(尽管它们没有static 修饰符)。在用到常量的所有地方,编译器将把常量替换为其真实的值
方法
方法是与特定类相关联的函数
属性
属性是可以从客户端访问的函数,其访问方式与访问类的公共字段类似。C#为实现类中的读写属性提供了专用语法,所以不必使用那些名称中嵌有Get或Set的方法。因为属性的这种语法不同于一般函数的语法,所以在客户端代码中,对象是实际的东西的错觉被加强了
构造函数
构造函数是在实例化对象时自动调用的特殊函数。它们必须与所属的类同名,且不能有返回类型。构 造函数对于初始化很有用
索引器
索引器允许使用访问数组的方式访问对象。
运算符
运算符执行的最简单的操作就是加法和减法。在两个整数相加时,严格地说,就是对整数使用“+” 运算符。C#还允许指定把已有的运算符应用于自己的类(运算符重载)。
事件
事件是类的成员,在发生某些行为(如修改类的字段或属性,或者发生了某种形式的用户交互操作)时,它可以让对象通知订阅者。客户端可以包含称为“事件处理程序”的代码来响应该事件。
析构函数
析构函数或终结器的语法类似于构造函数的语法,但是在CLR检测到不再需要某个对象时调用。它们的名称与类相同,但前面有一个“~”符号。不可能精确预测什么时候调用终结器。
解构函数
解构函数允许将对象解构为元组或不同的变量。
类型
类可以包含内部类。如果内部类型只和外部类型结合使用,将会得到有趣的结果
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GiselleLu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值