值类型和引用类型
值类型
所有的值类型都直接或间接地继承自System.ValueType,值类型的变量包含类型的实例,在赋值、传参、作为返回值的时候,都会直接拷贝其完整的字段 —— 即赋值完整的实例,所以对副本进行的修改不会对原始数据进行改动。
基本上,值类型的内存分配以及操作是直接在栈区实现
常见的值类型数据类型:
- 结构类型
- 内置数据类型(也称之为简单类型):
- 整型
- 浮点型
boolchar
- 值元组
struct
- 内置数据类型(也称之为简单类型):
- 枚举类型
enum
引用类型
引用类型存储的是数据的地址,然而实际数据存放在堆中,所以当一个引用类型被赋值给其他变量时,会拷贝一份引用(即生成两份别名),所以对副本的修改也会使得原始数据(被引用的对象)被修改。
常见的引用类型:
- 内置引用类型
dynamicobjectstring
- 声明引用类型使用的关键字
classinterfacedelegaterecord
一点简单例子
C#
Person Tom = new Person("Tom");
var temp = Tom;
temp.Name = "Jack";
Console.WriteLine(Tom.Name);假设有一个Person类,其中封装了Name成员。在上述代码中 ——
- 实例化了一个
Tom对象,并且在堆区为它进行了内存的分配 - 创建了一个
temp引用对象,指向Tom在堆区的内存 - 直接修改
temp,等价于直接修改Tom在堆区的数据 - 所以最后输出
Tom.Name为Jack
IMPORTANT
- 如果不是对引用对象进行内部成员的修改,是不会操作堆中数据的;
- 直接对一个引用对象赋值,相当于指向了另一个已实例化对象在托管堆中的数据
Method Modifiers
ref, out, in
其底层定义都是T&,使用指针传递,所以对于大型的值类型(比如超大结构体)来说,使用这些来进行传参,可以提高效率(少了一次复制)
ref
ref关键字有点类似在 C 中使用取地址符使得一个函数直接对实参进行操作 —— 在C#中,即为把方法内部对参数的修改回传给调用者
C#
void Add5(ref int x) => x += 5;
int x = 0;
Add5(ref x);
Console.WriteLine(x);使用ref修饰参数,使得最后x的值为5 ( 0 + 5 = 5 )
注意!——
- 声明与调用都要加
ref关键字。 - 调用前变量必须已被初始化,否则编译报错。
- 方法内既能读取也能写回调用者的变量。
底层原理:
- IL 中的参数类型为
T&(ByRef)。 - 传入的是变量的地址,JIT 在栈或寄存器上传递这个地址,方法直接操作该内存。
out
常用于Try-Prase等模式,其和ref有点类似,都是作为引用传递。但是必须在函数体内对out修饰的参数进行赋值。
例子
C#
bool TryPrase(string s, out int x)
{
try
{
int.TryParse(s, out x);
return true;
}
catch
{
x = 0;
return false;
}
}
string s = "31";
if (TryPrase(s, out int x))
Console.WriteLine($"{s} convert to {x}");in
in关键字让参数按引用传递且只读(函数体内无法修改)