值类型和引用类型
值类型/引用类型包含有:
值类型:int、double、bool、char、decimal、struct、enum
引用类型:string、自定义类、数组、集合、object、接口
二者的区别:
1、值类型和引用类型在内存上存储的地方不同:
存储:
值类型的值是存储在内存的栈当中。
引用类型的值是存储在内存的堆中。
2、在传递值类型和传递引用类型时,传递方式不同;
1.值类型的按值传递
按值传递时,传递过去的是该值类型实例的一个拷贝。栈数据会完整地复制到目标参数中即实参和形参中的数据相同但存放在内存的不同位置.
2.引用类型的按值传递
是把它的引用在栈上复制出来一份,然后传递给方法。这样就造成了栈上的两个引用指向了托管堆上的同一个实例。
3.值类型的按引用传递
按引用传递的时候是不存在拷贝这步操作的,众所周知,值类型的实例是分配在栈上的,所以在按引用传递值类型的时候,其实是把该实例在栈上的地址,传递给了方法。
4.引用类型的按引用传递
引用类型的按引用传递过程,与值类型的相似,也不存在拷贝这步操作,只是将“该实例的引用”在栈上的地址,传递给了方法。
值类型的传递
值类型在传递的时候,传递的是值本身。
using System;
namespace 值传递和引用传递
{
class Program
{
static void Main(string[] args)
{
int a = 1;
int b = a;
b = 2;
Console.WriteLine("a={0}",a);
Console.WriteLine("b={0}",b);
}
}
}
运行结果:
内存图:
在执行 int b=a; 时,在栈中开辟一块空间用于存储b,并将a中的值传给b。执行完 b=2; 后,b被赋予了新值,并覆盖了原来的值。根据输出结果,值传递后,b的值与a的值无关,互不影响。
引用类型的传递
注意:这里引用传递的运行结果放在string类型上会有所不同,这是因为string类型具有不可变性,string类型的不可变性在下文会有介绍。
引用类型传递的时候,传递的是对这个对象的引用(即存在堆中的地址)。
下面以自定义类(Person)来说明:
using System;
namespace 值传递和引用传递
{
class Program
{
static void Main(string[] args)
{
Person p1 = new Person();
p1.Name = "张三";
Person p2 = p1;
p2.Name = "李四";
Console.WriteLine("p1.Name={0}",p1.Name);
Console.WriteLine("p2.Name={0}", p2.Name);
}
public class Person
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
}
}
运行结果:
内存图:
创建完成p1对象后,在堆上开辟一块存储空间,并将该存储空间的地址放在栈上,栈中这块空间的地址(标识)是p1。当p1对象传递给p2后,只是将对堆中的存储的引用(堆中的地址)复制一份传给p2,完成传递后p1和p2共同指向同一块内存,所以p1或p2中任意一个更改存储内容后,另一个也会改变。
string类型的不可变性
using System;
namespace 值传递和引用传递
{
class Program
{
static void Main(string[] args)
{
string a = "张三";
string b = a;
b = "李四";
Console.WriteLine("a={0}", a);
Console.WriteLine("b={0}", b);
}
}
}
运行结果:
从运行结果可以看出,string类型的传递和一般引用类型不同,这是因为string类型具有不可变性:
字符串的不可变性指的是当你给一个字符串重新赋值之后,旧的值并没有被销毁,而是重新开辟一块空间存储新值。
当程序结束后,GC扫描整个内存,如果发现有的空间没有被指向,则立即把它销毁。
字符串的不可变性核心原因是因为String内部的Char [ ] value属性是final修饰的,是不可变的。
内存图:
在a赋值“张三”并传递给b后,根据“引用类型的传递”,a和b共同指向“张三”(如图:堆地址为10001)这块内存。但是,在b重新被赋值为“李四”后,堆中开辟一块空间存储“李四”,旧值“张三”依然存在,新值的堆地址(如图:堆地址为10002)赋给栈中b的存储以使b指向“李四”。至此,a和b就不再指向同一块堆空间了。故运行结果与一般引用类型有所不同。
实战:
引用类型值传递
在RefreshTimerControl方法中lastTimerControl= nowTimerControl; 等于lastTimerControl重新指向了托管堆上的另外一个实例,如果不使用ref的话,在RefreshTimerControl方法执行后 mUpTime还是null。因此必须使用ref
//稼动率
mUpTime = null;
RefreshTimerControl(ref mUpTime, StringHelper.UpTime);
private void RefreshTimerControl(ref TimerControl lastTimerControl, string nowTimerControlName)
{
var nowTimerControl = SysBaseManager.Instance.TimerControlList.Where(p => p.Name == nowTimerControlName).FirstOrDefault();
//此次不为空
if (nowTimerControl != null && nowTimerControl.Enable)
{
//上一次获取的定时器信息与当前获取的定时器信息不一致
if (lastTimerControl == null)
{
//上一次为空,首先初始化
InitTimerControlTimer(nowTimerControl);
}
else
{
//上一次不为空
if (lastTimerControl.ToJsonString() != nowTimerControl.ToJsonString())
ReOpenTimerControlTimer(nowTimerControl);
}
lastTimerControl = nowTimerControl;
}
else
{
//此次为空,上一次不为空
if (lastTimerControl != null)
{
CloseTimerControlTimer(lastTimerControl);
lastTimerControl = null;
}
}
}
String类型值传递
var message = string.Empty;
ret = MesBulkDal.deleteWorkOrderList(postData.data,message);
break;
public static int deleteWorkOrderList(string data,string message)
{
message = "测试";
return Code.Fail;
}
运行结果:
String类型不可变性赋值
string str1 = "hello";
str1 = "hello world";
string str1 = "hello";
执行完毕后 栈地址是0x00aff088 堆地址0x2d92424
str1 = "hello world";
执行完毕后 栈地址是0x00aff088 堆地址0x2d9243c