概述
C#中的Struct是值类型数据结构,在使用时常发生值拷贝。
公用代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public struct ChildStruct { public int Value; }
public class ChildClass { public int Value; }
public struct ParentStruct { public int Value; public ChildStruct ChildStruct; public ChildClass ChildClass; }
|
作为参数进行值拷贝
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| static void Main(string[] args) { ParentStruct parentStruct1 = new ParentStruct(); parentStruct1.Value = 1; Console.WriteLine(parentStruct1.Value);
CopyValue1(parentStruct1);
Console.WriteLine(parentStruct1.Value); }
public static void CopyValue1(ParentStruct parentStruct) { parentStruct.Value = 2; }
|
输出结果:
分析
函数CopyValue1
中对传入参数parentStruct
的修改,并不能影响函数外parentStruct1
的值。所以认为Struct
作为传入参数发生了值拷贝,函数内修改的与函数修改的Struct
并非同一个引用。
改良
使用ref
进行传参,这样不再传递Struct
的值,而是传递Struct
的引用来避免值拷贝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| static void Main(string[] args) { ParentStruct parentStruct1 = new ParentStruct(); parentStruct1.Value = 1; Console.WriteLine(parentStruct1.Value);
CopyValue1(ref parentStruct1);
Console.WriteLine(parentStruct1.Value); }
public static void CopyValue1(ref ParentStruct parentStruct) { parentStruct.Value = 2; }
|
return进行值拷贝
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| static void Main(string[] args) { ParentStruct parentStruct1 = new ParentStruct(); parentStruct1.Value = 1;
ParentStruct parentStruct2 = CopyValue2(ref parentStruct1); parentStruct2.Value = 3;
Console.WriteLine(parentStruct1.Value);
Console.WriteLine(parentStruct2.Value); }
public static ParentStruct CopyValue2(ref ParentStruct parentStruct) { parentStruct.Value = 2; return parentStruct; }
|
输出结果
分析
从之前的示例可以得知ref传参传递的是引用,因此认为函数内的parentStruct
变量的与函数外的parentStruct1
变量是同一个引用。
变量parentStruct2
接收CopyValue2
的返回值,并对parentStruct2
中的值进行修改,但输出的结果发现parentStruct1
中的值并未发生修改。
可以得出结论:函数的返回值也是值传递,并非引用传递。
改良
使用ref
将返回的类型为引用类型。接收的变量也需要使用ref
修饰。来避免值拷贝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| static void Main(string[] args) { ParentStruct parentStruct1 = new ParentStruct(); parentStruct1.Value = 1;
ref ParentStruct parentStruct2 = ref CopyValue1(ref parentStruct1); parentStruct2.Value = 3;
Console.WriteLine(parentStruct1.Value);
Console.WriteLine(parentStruct2.Value); }
public static ref ParentStruct CopyValue1(ref ParentStruct parentStruct) { parentStruct.Value = 2; return ref parentStruct; }
|
Struct类型数组元素存为变量时进行值拷贝
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| static void Main(string[] args) { ParentStruct[] structs = new ParentStruct[1];
structs[0] = new ParentStruct(); structs[0].Value = 1;
Console.WriteLine(structs[0].Value);
ParentStruct parentStruct0 = structs[0];
parentStruct0.Value = 2;
Console.WriteLine(structs[0].Value); Console.WriteLine(parentStruct0.Value); }
|
输出结果
分析
由输出的结果可以发现对于将Struct数组
的元素存为变量parentStruct0
后,对于parentStruct0
的修改并不会改变数组中元素内的值,所以拷贝的是值而不是引用。
改良
使用ref
修饰变量,这样变量存储的是数组元素的引用来避免值拷贝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| static void Main(string[] args) { ParentStruct[] structs = new ParentStruct[1];
structs[0] = new ParentStruct(); structs[0].Value = 1;
Console.WriteLine(structs[0].Value);
ref ParentStruct parentStruct0 = ref structs[0];
parentStruct0.Value = 2;
Console.WriteLine(structs[0].Value); Console.WriteLine(parentStruct0.Value); }
|
函数内创建并返回的Struct避免值拷贝
示例代码
1 2 3 4 5 6 7 8 9 10
| static void Main(string[] args) { ParentStruct parentStruct1 = CreateParentStruct(); }
public static ParentStruct CreateParentStruct() { ParentStruct parentStruct = new ParentStruct(); return parentStruct; }
|
分析
从之前的结论可以得知函数返回值是值拷贝,所以使用out
关键词来避免值拷贝
改良
1 2 3 4 5 6 7 8 9 10
| static void Main(string[] args) { ParentStruct parentStruct1; CreateParentStruct(out parentStruct1); }
public static void CreateParentStruct(out ParentStruct parentStruct) { parentStruct = new ParentStruct(); }
|
Struct内的Struct类型成员与Class类型成员
ParentStruct
是个Struct
,内部有Struct
类型成员变量ChildStruct
和Class
类型成员变量ChildClass
。分别创建这两种类型的变量,初始化Value
的值,并赋值给ParentStruct
变量对应的成员。两种类型的变量的Value
值,观察ParentStruct
变量内部对应成员值的变化。代码如下:
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
| static void Main(string[] args) { ParentStruct parentStruct1 = new ParentStruct();
ChildStruct childStruct1 = new ChildStruct(); childStruct1.Value = 1;
parentStruct1.ChildStruct = childStruct1;
Console.WriteLine($"childStruct1.Value:{childStruct1.Value}");
childStruct1.Value = 2; Console.WriteLine("-------childStruct1.Value的值修改后-------");
Console.WriteLine($"childStruct1.Value:{childStruct1.Value}"); Console.WriteLine($"parentStruct1.ChildStruct.Value:{parentStruct1.ChildStruct.Value}");
Console.WriteLine("\n");
ChildClass childClass1 = new ChildClass(); childClass1.Value = 3;
parentStruct1.ChildClass = childClass1;
Console.WriteLine($"childClass1.Value:{childClass1.Value}");
childClass1.Value = 4; Console.WriteLine("-------childClass1.Value的值修改后-------");
Console.WriteLine($"childClass1.Value:{childClass1.Value}"); Console.WriteLine($"parentStruct1.ChildClass.Value:{parentStruct1.ChildClass.Value}"); }
|
输出结果
1 2 3 4 5 6 7 8 9 10
| childStruct1.Value:1 -------childStruct1.Value的值修改后------- childStruct1.Value:2 parentStruct1.ChildStruct.Value:1
childClass1.Value:3 -------childClass1.Value的值修改后------- childClass1.Value:4 parentStruct1.ChildClass.Value:4
|
分析
Struct的Struct成员存储的是成员内部的值,而Struct的Class成员存储的是成员的引用。并且在C#11之前的版本没法使用ref成员。因此Struct类型的成员没法使用享元模式。因为每次都会拷贝。
疑问
对于C#的ref关键词,个人认为ref在修饰参数时和作用与类型上时是不一样的。
修饰参数时表示参数是通过引用的方式来传递的,但没法传递一个数组的元素的引用。只能传递数组元素的值拷贝,在内部对引用的变更并不会影响到外部。对此感到疑惑,因此需要慎重在方法内对于ref或out修饰的参数的引用进行修改。