传参
c# 和 c++ 感觉很不同的一点就是关于 new 的使用,在 c++ 中,new 只是动态分配一块内存,比如 Person p(10,"zhangqr");
和 Person *p = new Perspn(10,"zhangqr");
前者 p 就是一个实体,后者 p 只是一个指向实体的指针。而在 c# 中,Person p;
表示申明了一个指向 null 的 Person 类型指针,想要在申明时就初始化需要使用 Person p = new Person(10,"zhangqr");
,这里的 p 本质上是一个指针。
1 | using System; |
运行结果:
product 被构造1次, name:Fasteners,id:54321
Original values in Main. Name: Fasteners, ID: 54321product 被构造2次, name:Stapler,id:99999
Back in Main. Name: Fasteners, ID: 54321
解释:
在函数传递的时候拷贝构造函数也没有被调用,这一点也跟 c++ 不一样,但理解了其实传递的是指针之后就会明白,其实还是一样的,拷贝构造是在用一个实例创建另一个实例的时候会调用,但其实从图中可以看出来从头到尾只有一个实例,增加的也只是指针而已。
如果将 itemRef = new Product("Stapler", 99999);
注释掉的话,运行结果如下,这个从图中也很好理解,最后就是两个指针指向同一个实例。
product 被构造1次, name:Fasteners,id:54321
Original values in Main. Name: Fasteners, ID: 54321Back in Main. Name: Fasteners, ID: 12345
将两个 ref 的注释去掉之后,运行结果为:
product 被构造1次, name:Fasteners,id:54321
Original values in Main. Name: Fasteners, ID: 54321product 被构造2次, name:Stapler,id:99999
Back in Main. Name: Stapler, ID: 12345
解释:
当传参的时候申明了 ref ,我是将其理解成 c++ 的引用,也操作的是同一个指针,但是有两个不同的名字。
以上并未加验证,本来是想通过 unsafe
来取地址验证一下,但是 unsafe
需要在声明时就加上 * ,也就是像 c++ 的写法一样,而我是想用 c# 的写法来取到地址,所以暂时没找到验证方法,因为目前猜测都合理,所以暂且这么理解。
readonly 与 ref
先上实验代码
1 | using System.Collections; |
运行结果为:
- 使用声明时的赋值,value types:10,reference types:a=10,b=20
- 使用构造函数中的赋值,value types:15,reference types:a=15,b=25
- 局部变量是否与 reference type 相同(Equals函数)True
- 改变了局部变量所指的对象,value types:15,reference types:a=25,b=35,局部变量:a=25,b=35
- 局部变量是否与 reference type 相同(Equals函数)True
- 改变 reference type 所指的对象,value types:15,reference types:a=35,b=45,局部变量:a=35,b=45
- 改变了局部变量的指向后,是否还与 reference type 相同(Equals函数)False
- 改变了局部变量的指向后,value types:15,reference types:a=35,b=45,局部变量:a=45,b=55
上面结果都是合理的,然后我在传参的时候将引用类型设置为了 ref,具体是:
data = new ReadOnlyData(15, dataB);
改为data = new ReadOnlyData(15, ref dataB);
public readonly ReadOnlyDataB refType;
改为public readonly ReadOnlyDataB refType = new ReadOnlyDataB { a = 10, b = 20 };
public ReadOnlyData() :this(10,new ReadOnlyDataB { a=10,b=20})
改为public ReadOnlyData() // :this(10,new ReadOnlyDataB { a=10,b=20})
public ReadOnlyData(int age,ReadOnlyDataB refType)
改为public ReadOnlyData(int age,ref ReadOnlyDataB refType)
按我的设想,此时成员变量 refType
、ReadOnlyData 的二参 refType
和局部变量data
应该都是同一个指针,只是有不同的别名而已,所以在dataB = new ReadOnlyDataB { a = 45, b = 55 };
之后,三者所指向的实例应该是一样的,结果并不是,运行结果是跟一次完全一样的,在梳理了 ref 的用法之后才惊觉ReadOnlyData 的二参 refType
和局部变量data
是同一个指针,但成员变量 refType
并不是,因为他是直接用赋值符号得到的。验证如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ReadonlyTest : MonoBehaviour
{
public void OnGUI()
{
if(GUILayout.Button("测试 readonly "))
{
// 使用声明 readonly 成员变量时赋的值
ReadOnlyData data = new ReadOnlyData();
Debug.LogFormat("使用申明时的赋值,value types:{0},reference types:a={1},b={2}", data.age,data.refType.a,data.refType.b);
// 使用构造函数给 readonly 成员变量赋值
ReadOnlyDataB dataB = new ReadOnlyDataB { a = 15, b = 25 };
data = new ReadOnlyData(15, ref dataB);
Debug.LogFormat("使用构造函数中的赋值,value types:{0},reference types:a={1},b={2}", data.age,data.refType.a, data.refType.b);
// 改变局部变量的值
//Debug.Log("局部变量是否与 reference type 相同(Equals函数)"+ dataB.Equals(data.refType));
//dataB.a = 25;
//dataB.b = 35;
//Debug.LogFormat("改变了局部变量所指的对象,value types:{0},reference types:a={1},b={2},局部变量:a={3},b={4}",
// data.age, data.refType.a, data.refType.b,dataB.a,dataB.b);
//// 改变 reference type 所指的值
//Debug.Log("局部变量是否与 reference type 相同(Equals函数)" + dataB.Equals(data.refType));
//data.refType.a = 35;
//data.refType.b = 45;
//Debug.LogFormat("改变 reference type 所指的对象,value types:{0},reference types:a={1},b={2},局部变量:a={3},b={4}",
// data.age, data.refType.a, data.refType.b, dataB.a, dataB.b);
// 改变局部变量的指向
// dataB = new ReadOnlyDataB { a = 45, b = 55 };
Debug.Log("改变了局部变量的指向后,是否还与 reference type 相同(Equals函数)" + dataB.Equals(data.refType));
Debug.LogFormat("改变了局部变量的指向后,value types:{0},reference types:a={1},b={2},局部变量:a={3},b={4}",
data.age, data.refType.a, data.refType.b, dataB.a, dataB.b);
// data.refType = new ReadOnlyDataB { a = 45, b = 55 };// 无法对只读字段进行赋值,初始化和构造函数除外。
// data1.age = 20; // 无法对只读字段进行赋值,初始化和构造函数除外。
}
}
}
public class ReadOnlyData
{
// readonly 变量直接在申明的时候赋值。
public readonly int age = 10;
// reference types 用 readonly 修饰
public readonly ReadOnlyDataB refType = new ReadOnlyDataB { a = 10, b = 20 };
public ReadOnlyData(int age,ref ReadOnlyDataB refType)
{
refType = new ReadOnlyDataB { a = 100, b = 101 };
this.age = age; // 可以, readonly 变量可以在构造函数中赋值。
this.refType = refType;
this.refType.a = 120;
this.refType.b = 121;
}
public ReadOnlyData() // :this(10,new ReadOnlyDataB { a=10,b=20})
{
}
public void ChangeAge(int age)
{
// this.age = age; // 错误,除了申明和构造函数中不能对 readonly 赋值
}
public void ChangeReferenceValue()
{
// this.refType = new ReadOnlyDataB { a = 45, b = 55 };// 错误,除了申明和构造函数中不能对 readonly 赋值
}
}
public class ReadOnlyDataB
{
public int a;
public int b;
}
运行结果:
- 使用申明时的赋值,value types:10,reference types:a=10,b=20
- 使用构造函数中的赋值,value types:15,reference types:a=120,b=121
- 改变了局部变量的指向后,是否还与 reference type 相同(Equals函数)True
- 改变了局部变量的指向后,value types:15,reference types:a=120,b=121,局部变量:a=120,b=121
解释如图:
将 ref 修饰词给去掉之后:
- 使用申明时的赋值,value types:10,reference types:a=10,b=20
- 使用构造函数中的赋值,value types:15,reference types:a=120,b=121
- 改变了局部变量的指向后,是否还与 reference type 相同(Equals函数)False
- 改变了局部变量的指向后,value types:15,reference types:a=120,b=121,局部变量:a=15,b=25
解释如图: