值类型与引用类型
值类型:派生自System.ValueType类的类型是值类型,派生自ValueType的类型都会自动在栈(stack)上进行分配,因此有一个可预测的生命周期,而且非常高效。
引用类型:在继承链上没有System.ValueType的类型(如System.Type、System.String、System.Array、System.Exception以及System.Delegate)不会在栈上分配,而是在垃圾回收堆(heap)上进行分配。
另:C#中的所有基类型都是结构类型(例如:int对应System.Int32结构),结构类型是值类型;类类型是引用类型;栈的执行效率要比堆的执行效率高,可是栈的资源有限,不适合处理大的逻辑复杂的对象。所以结构处理作为基类型对待的小对象,而类处理某个商业逻辑;因为结构是值类型所以结构之间的赋值可以创建新的结构,而类是引用类型,类之间的赋值只是复制引用;(所以在以结构为参数传递时,最好使用ref,这样只传递地址引用,能够提高效率,同时也应注意这样结构的值也会随着方法调用而改变)
从功能上说,ValueType的唯一目的是"重写"由Object定义的虚方法来使用基于值而不是基于引用的语法:
{
protected ValueType();
public override bool Equals( object obj);
public override int GetHashCode();
public override string ToString();
}
由于值类型是基于值语法的,结构(包括所有数值数据类型如Int32等、自定义结构、枚举)的生命周期都是可预测的。但结构离开定义的作用域时,就会立即从内存中清除。
{
int i = 0 ; // int是System.Int32结构
Point p = new Point(); // Point是自定义结构
}
值类型与类类型的赋值
当把值类型赋值给另外一个时,是对字段成员逐一进行复制。对于System.Int32这样简单的数据类型,唯一需要复制的成员就是数值。而对像Point复杂点的自定义结构,就需要把Point所有的字段复制到新的结构变量中。改变新的结构变量的字段的值不会影响原结构变量的字段值。
当对引用类型进行复制时,是把原引用变量的地址指向赋给新的引用变量,两个引用变量指向同一个对象。
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
struct Point
{
public int X;
public int Y;
public Point( int XPos, int YPos)
{
X = XPos;
Y = YPos;
}
public void Increment()
{
X ++ ; Y ++ ;
}
public void Decrement()
{
X -- ; Y -- ;
}
public void Display()
{
Console.WriteLine( " X = {0}, Y = {1} " , X, Y);
}
}
// PointRef类型的定义
class PointRef
{
public int X;
public int Y;
public PointRef( int XPos, int YPos)
{
X = XPos;
Y = YPos;
}
public void Increment()
{
X ++ ; Y ++ ;
}
public void Decrement()
{
X -- ; Y -- ;
}
public void Display()
{
Console.WriteLine( " X = {0}, Y = {1} " , X, Y);
}
}
static void Main( string [] args)
{
Console.WriteLine( " ***** 值类型 / 引用类型 *****\n " );
ValueTypeAssignment();
ReferenceTypeAssignment();
Console.ReadLine();
}
static void ValueTypeAssignment()
{
Console.WriteLine( " 指定值类型\n " );
Point p1 = new Point( 10 , 10 );
Point p2 = p1;
p1.Display();
p2.Display();
p1.X = 100 ;
Console.WriteLine( " \n=> 改变 p1.X\n " );
p1.Display();
p2.Display();
}
static void ReferenceTypeAssignment()
{
Console.WriteLine( " 指定引用类型\n " );
PointRef p1 = new PointRef( 10 , 10 );
PointRef p2 = p1;
p1.Display();
p2.Display();
p1.X = 100 ;
Console.WriteLine( " \n=> 改变 p1.X\n " );
p1.Display();
p2.Display();
}
![2011030111053129.jpg](https://i-blog.csdnimg.cn/blog_migrate/8542a97a7a97af7625ed1b559fcfc6ed.jpeg)
包含引用类型的值类型
默认情况下,但值类型包含其他引用类型时,赋值将产生一个引用副本。这样就有两个独立的结构,每个都包含指向内存中同一个对象的引用(也就是浅复制)。当先执行深度赋值(将引用的对象的状态完全复制到新的对象中)时,引用类型就需要实现ICloneable结构。
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
public string infoString;
public ShapeInfo( string info)
{ infoString = info; }
}
struct Rectangle
{
public ShapeInfo rectInfo;
public int rectTop, rectLeft, rectBottom, rectRight;
public Rectangle( string info, int top, int left, int bottom, int right)
{
rectInfo = new ShapeInfo(info);
rectTop = top; rectBottom = bottom;
rectLeft = left; rectRight = right;
}
public void Display()
{
Console.WriteLine( " String = {0}, Top = {1}, Bottom = {2}, Left = {3}, Right = {4} " ,
rectInfo.infoString, rectTop, rectBottom, rectLeft, rectRight);
}
}
static void Main( string [] args)
{
Console.WriteLine( " ***** 值類型 / 引用類型 *****\n " );
ValueTypeContainingRefType();
Console.ReadLine();
}
static void ValueTypeContainingRefType()
{
Console.WriteLine( " -> 創建 r1 " );
Rectangle r1 = new Rectangle( " 初始的信息 " , 10 , 10 , 50 , 50 );
Console.WriteLine( " -> 把r1賦給r2 " );
Rectangle r2 = r1;
Console.WriteLine( " -> 改變r2的值 " );
r2.rectInfo.infoString = " 新的字符信息! " ;
r2.rectBottom = 4444 ;
r1.Display();
r2.Display();
}
![2011030111282618.jpg](https://i-blog.csdnimg.cn/blog_migrate/4914a0bbbaae8843192c5511e465c470.jpeg)
可以看出,当使用r2的引用改变信息字符串的值时,r1的引用显示了同样的值(r1的引用的值也改变了)。
按值转递引用类型与按引用转递引用类型
引用类型可以作为参数传递给类型成员。但是,按按值转递一个对象与按引用转递一个对象大有不同。
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
// 按值傳遞
Console.WriteLine( " *********按值傳遞引用類型對象***** " );
Car c = new Car( " 寶馬 " , 100 );
Console.WriteLine( " 按值傳遞前對象的狀態。 " );
c.Print();
SendACarByValue(c);
Console.WriteLine( " 按值傳遞後對象的狀態。 " );
c.Print();
Console.ReadLine();
// 按引用傳遞
Console.WriteLine( " *********按值傳遞引用類型對象***** " );
Car bmw = new Car( " 寶馬 " , 100 );
Console.WriteLine( " 按引用傳遞前對象的狀態。 " );
bmw.Print();
SendACarByReference( ref bmw);
Console.WriteLine( " 按引用傳遞後對象的狀態。 " );
bmw.Print();
Console.ReadLine();
}
static void SendACarByValue(Car c)
{
// 改變c的Speed屬性的值
c.Speed = 150 ;
// 給c重新賦值(賦予新的Car對象的指向)
c = new Car( " 保時捷 " , 250 );
}
static void SendACarByReference( ref Car c)
{
// 改變c的Speed屬性的值
c.Speed = 150 ;
// 給c重新賦值(賦予新的Car對象的指向)
c = new Car( " 保時捷 " , 250 );
}
class Car
{
public int Speed { get ; set ; }
public string Name { get ; set ; }
public Car( string n, int s)
{
this .Name = n;
this .Speed = s;
}
public Car() { }
public void Print()
{
Console.WriteLine( " 汽車{0}的速度是{1} " , this .Name, this .Speed);
}
}
可以看出:
一、如果按值传递引用类型,被调用者可能改变对象的状态数据的值,但不能改变所引用的对象(不能指向另一个新的对象,即指向没有改变)。
二、如果按引用传递引用类型,被调用者可能改变对象的状态数据的值和所引用的对象(指向都改变了)。
小结
值类型与引用类型的比较:
问题 | 值类型 | 引用类型 |
这个类型分配在哪里? | 分配在栈(stack)上 | 分配在托管堆(heap)上 |
变量是怎么表示的? | 是副本 | 变量指向被分配的对象所占的内存 |
能否作为其他类型的基类? | 不能,是封闭(sealed)的 | 能,若没有声明是封闭时可以。 |
能为这个类型定义构造函数吗? | 能,但默认的构造函数被保留 (即自定义构造函数必须全部带有参数) | 能 |
这个类型的变量什么时候消亡? | 当超出定义它们的作用域时 | 当托管堆被垃圾回收器回收时 |