C#中引用类型继承自System.Object.引用类型的对象的值存放在托管堆上,在栈中只存放对它的索引(地址)。因为这个原因,引用类型对象之间的赋值操作默认情况下都是浅拷贝,只拷贝栈中的一份索引,它们指向一块相同的托管堆内存。如Person p=new Person();Person p1=p;第二个赋值语句的意思就是让p1指向p所指向的对象,即二者共同指向一个Pernson的实例new Person()。可是这句话说起来简单,但是应该不是那么好理解的,至少对我来说很长一段时间是这样。下边谈谈本人的拙见,学的东西有限,欢迎指正。
我们可以问自己几个问题:
1、p1和p既然指向同一个对象,那么他们本身也就是同一个对象?
2、既然他们指向同一个对象,那么对p指向的对象做了修改也会导致p1指向的对象改变?
3、如果问题二答案是肯定的,那么也就是说操作p对象也会同样操作p1对象?
4、如果问题三的答案是肯定的,那么对引用类型对象的任意操作就会同时更改另一个和它具有相同类型并且指向同一个对象的对象的值?也就是说ref关键字对于引用类型的参数传递没有意义?(因为按照这种逻辑引用类型的变量在方法中的操作同样会反应到方法外边来,无需使用ref关键字也能达到这样的效果)带着这几个问题,浅析下c#中引用类型的“引用”。
先贴段代码吧。
class Person
{
publicPerson(int age, stringname) { this.age = age; this.name = name; }
public int age;
public string name;
public void DoSomthing() { Console.WriteLine("我的name是{0},age是{1}", name, age); }
}
class Program
{
static void Main(string[]args)
{
Personp = new Person(100,"张三");
TestRef(p);
p.DoSomthing();
}
static void TestRef(Personp)
{
p.name = "李四";
}
}
运行结果:
图一
1、保持其它部分不变,将方法TestRef修改为以下代码
static void TestRef(Personp)
{
p = newPerson(99,"李四");
}
运行结果:
图二
2、保持其它部分不变,将方法TestRef修改为以下代码
static void TestRef(ref Person p)
{
p = newPerson(99,"李四");
}
运行结果:
图三
现在解释上边代码的运行结果并回答开头的四个问题。
三段代码中p都是引用类型Person的对象,调用方法TestRef的本质是将Main方法中实参p的值赋值给TestRef方法的形参p,这种赋值在前边已说过,只是浅层拷贝,两个p都指向同一个对象,所以在方法中对p指向的对象进行操作 p.name= "李四" 将同样反应到方法外边来。但即使是这样,两个p并不是同一个对对象,也就是说Person.ReferenceEquals对于这两个对象来说是false。说的直白点,形参p在栈中地址和实参p在栈中的地址并不相同。一个很简单的例子,Personp=new Person();Person p1=p;p=null;在进行了这段操作之后难道p1也为null?答案显然是否定的。
这样就解释了图一和图二的运行结果以及问题一的答案。
至于图三,因为加了ref关键字,所以在实参和形参传值时的并不是做简单的拷贝,而是让形参直接和实参共用同一段堆内存,也就是说他们现在是同一个对象,这点就像是c语言中指针存放的地址和它指向的变量的地址的关系一样(注意:并不是指针本身的地址而是它存放的地址也就是他的值)。所以形参和实参完全是等价的。这就是ref关键字的价值所在。
这样也回答了问题四。
关于问题二,答案时肯定的,因为图一中修改了形参p所指向的对象的值,也同时修改了实参p所指向对象的值。但是之中修改也是有限制的。假如当修改p1所指对象的值时p已经指向了一个新的对象,那么这个修改就不会在p中反应出来,所以如果要想将引用类型对象的值的修改反应到方法体外边来而又不想用ref关键字,那么请勿在方法体内改变形参的指向,诸如 p=new Person,p=null之类的操作都会改变p的指向。这样的操作对方发体外的实参是无效的,因为他们并不是同一个对象,他们仅仅是指向同一个对象而已。
关于问题三,其实在上边已作了回答。答案当然是否定的,它们又不是同一个对象,操作对象p和对象p1毛线关系都没有。只是说当p和p1指向同一个对象时,对p的操作有可能是对p所指向对象的操作(如p.name=”李四”,但也有可能不是,如p=new Person()),那么也当然会操作到p1所指向的对象,这样就会反应到p对象上来,仅此而已。
补充一点,对引用类型对象的赋值操作 如:Personp=new person(),实际的做的事情大概是在托管堆上创建Person 类的对象new Person()并返回该对象的指针,然后在栈上创建Person类型的对象的引用,并使该引用的值为刚刚返回的指针值。
综上所述,我觉得说xx对象所指向的对象,追根朔源指的应该就是类似于new Person()的这个对象,其它对象可能会指向xx这个对象所指向的对象,即new Person。要搞清楚引用类型的各种细节,起码得搞清哪个对象是哪个。