一、值类型和引用类型的区别
上表区别中,关于值的存储方式:通常被分配在线程的堆栈上,表明还有个例:
(1). 在引用类型中嵌套值类型
(2). 值类型装箱
这两种情况下,值类型的实例会被分配到托管堆中。
1. 引用类型中嵌套定义值类型
如果类的字段类型是值类型,它将作为引用类型实例的一部分,被分配到托管堆中。但那些作为局部变量(例如下列代码中的c变量)的值类型,则仍会被分配到线程堆栈中。
public class NestedValueTypeInRef
{
//valuetype作为引用类型的一部分被分配到托管堆上
private int valuetype = 3;
public void method( )
{
// c被分配到线程堆栈上
char c = 'c';
}
}
class Program
{
static void Main( string[ ] args )
{
NestedValueTypeInRef reftype = new NestedValueTypeInRef( );
}
}
2. 值类型嵌套定义引用类型
值类型嵌套定义引用类型时,堆栈上将保存该引用类型的引用,而实际的数据则依然保存在托管堆中。
public class TestClass
{
public int x;
public int y;
}
public class NestedRefTypeInValue
{
//结构体字段,注意,结构体中字段不能被初始化
private TestClass classinValuetype;
public NestedRefTypeInValue(TestClass t)
{
classinValuetype.x = 3;
classinValuetype.y = 5;
classinValuetype = t;
}
}
class Program
{
static void Main( string[ ] args )
{
NestedRefTypeInValue reftype = new NestedRefTypeInValue( new TestClass());
}
}
总结:值类型实例总会被分配到它声明的地方,声明的是局部变量时,将被分配到栈上,而声明为引用类型成员时,则被分配到托管堆上;而引用类型实例总是分配到托管堆上。
二、装箱与拆箱
装箱:将值类型转换为引用类型的过程
拆箱:将引用类型转换为值类型的过程
三、参数传递问题剖析
1. 值类型参数的按值传递
值类型的按值传递,传递的只是该值类型的一个副本,被调用方法操作的是实参的一个副本而已,方法对参数的改变不会影响到实参的值。
private static void Add( int add_num )
{
add_num += 1;
Console.WriteLine( "方法中的add_num值:" + add_num );
}
static void Main( string[ ] args )
{
Console.WriteLine( "值类型按值传递的情况" );
int addNum = 1;
Add( addNum );
Console.WriteLine( "调用方法后,实参addNum值:" + addNum );
Console.Read( );
}
运行结果:
2. 引用类型参数的按值传递
当传递的参数是引用类型时,传递和操作的目标是指向对象的地址,而传递的实际内容是对地址的复制。由于地址指向的是实参的值,当方法对地址进行操作时,实际上操作了地址所指向的值。调用方法后原来实参的值就会被修改。
public class RefClass
{
public int addNum;
}
class Program
{
//引用类型按值传递的情况,addnumRef为形参
private static void AddRef( RefClass addnumRef )
{
addnumRef.addNum += 1;
Console.WriteLine( "方法中的addNum的值:" + addnumRef.addNum );
}
static void Main( string[ ] args )
{
Console.WriteLine( "引用类型按值传递的情况" );
RefClass refClass = new RefClass( );
refClass.addNum = 1;
//refClass是实参,此时参数是引用类型
AddRef( refClass );
Console.WriteLine( "调用方法后,实参addNum值:" + refClass.addNum );
Console.Read( );
}
}
运行结果:
3. string引用类型参数按值传递的特殊情况
由于string具有不变性,一旦一个string类型被赋值,则它就是不可改变的,即不能通过代码去修改它的值。
public class RefClass
{
public int addNum;
}
class Program
{
//string引用类型按值传递的特殊情况
private static void ChangeStr( string oldStr )
{
oldStr = "New string";
Console.WriteLine( "方法中的oldStr的值:" + oldStr );
}
static void Main( string[ ] args )
{
Console.WriteLine( "string引用类型按值传递的特殊情况" );
string str = "old string";
ChangeStr( str );
Console.WriteLine( "调用方法后,实参str值:" + str );
Console.Read( );
}
}
运行结果:
4. 值类型和引用类型参数按引用传递
不管是值类型还是引用类型,都可以使用ref或out关键字来实现参数的按引用传递。并且在按引用传递时,方法的定义和调用都必须显示地使用ref或out关键字。
//值类型参数按引用传递的情况
private static void ChangeByValue( ref int numValue )
{
numValue = 10;
Console.WriteLine( numValue );
}
//引用类型参数按引用传递的情况
private static void ChangeByRef( ref string numRef )
{
numRef = "new string";
Console.WriteLine( numRef );
}
static void Main( string[ ] args )
{
Console.WriteLine( "值类型和引用类型参数按引用传递的情况" );
//num是值类型实参
int num = 1;
//refStr是引用类型实参
string refStr = "old string";
//值类型参数按引用传递
ChangeByValue( ref num );
Console.WriteLine( num );
//引用类型参数按引用传递
ChangeByRef( ref refStr );
Console.WriteLine( refStr );
Console.Read( );
}