600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > 理解C#值类型与引用类型

理解C#值类型与引用类型

时间:2021-02-13 16:05:42

相关推荐

理解C#值类型与引用类型

这篇文章是我几个月前写的,今天进行了比较大的修订,重新发了出来,希望和大家共同探讨,并在此感谢Anytao 的讨论和帮助。

从概念上看,值类型直接存储其值,而引用类型存储对其值的引用。这两种类型存储在内存的不同地方。在C#中,我们必须在设计类型的时候就决定类型实例的行为。这种决定非常重要,用《CLR via C# 》作者Jeffrey Richter的话 来 说,“不理解引用类型和值类型区别的程序员将会给代码引入诡异的bug和性能问题(I believe that a developer who misunderstands the difference between reference types and value types will introduce subtle bugs and performance issues into their code.)”。这就要求我们正确理解和使用值类型和引用类型。

1. 通用类型系统 2. 值类型 3. 引用类型 4. 值类型和引用类型在内存中的部署 4.1 数组在内存中的部署 4.2 值类型和引用类型的嵌套 5. 正确使用值类型和引用类型 5.1 辨明值类型和引用类型的使用场合 5.2 将值类型尽可能实现为具有常量性和原子性的类型 5.3 确保0为值类型的有效状态 5.4 尽量减少装箱和拆箱 6. 总结 7. 参考

1. 通用类型系统

C#中,变量是值还是引用仅取决于其数据类型。

C#的基本数据类型都以平台无关的方式来定义。C#的预定义类型并没有内置于语言中,而是内置于.NET Framework中。.NET使用通用类型系统(CTS)定义了可以在中间语言(IL)中使用的预定义数据类型,所有面向.NET的语言都最终被编译为 IL,即编译为基于CTS类型的代码。

例如,在C#中声明一个int变量时,声明的实际上是CTS中System.Int32的一个实例。这具有重要的意义:

确保IL上的强制类型安全; 实现了不同.NET语言的互操作性; 所有的数据类型都是对象。它们可以有方法,属性,等。例如: int i;

i = 1 ;

string s;

s = i.ToString();

MSDN的这张图 说明了CTS中各个类型是如何相关的。注意,类型的实例可以只是值类型或自描述类型,即使这些类型有子类别也是如此。

2. 值类型

C#的所有值类型均隐式派生自System.ValueType:

结构体:struct(直接派生于System.ValueType); 数值类型: 整 型:sbyte(System.SByte的别 名),short(System.Int16),int(System.Int32),long(System.Int64),byte(System.Byte),ushort(System.UInt16),uint(System.UInt32),ulong(System.UInt64),char(System.Char); 浮点型:float(System.Single),double(System.Double); 用于财务计算的高精度decimal型:decimal(System.Decimal)。 bool型:bool(System.Boolean的别名); 用户定义的结构体(派生于System.ValueType)。 枚举:enum(派生于System.Enum); 可空类型(派生于System.Nullable<T>泛型结构体,T?实际上是System.Nullable<T>的别名)。

每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值。例如:

int i = new int ();

等价于:

Int32i = new Int32();

等价于:

int i = 0 ;

等价于:

Int32i = 0 ;

使用new运算符时,将调用特定类型的默认构造函数并对变量赋以默认值。在上例中,默认构造函数将值0赋给了i。MSDN上有完整的默认值表 。

关于int和Int32的细节,在我的另一篇文章中有详细解释:《理解C#中的System.Int32和int 》。

所有的值类型都是密封(seal)的,所以无法派生出新的值类型。

值得注意的是,System.ValueType直接派生于System.Object。即System.ValueType本身是一个类类型,而 不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。

可以用Type.IsValueType属性来判断一个类型是否为值类型:

TestTypetestType = new TestType();

if (testTypetype.GetType().IsValueType)

{

Console.WriteLine( " {0}isvaluetype. " ,testType.ToString());

}

3. 引用类型

C#有以下一些引用类型:

数组(派生于System.Array) 用户用定义的以下类型: 类:class(派生于System.Object); 接口:interface(接口不是一个“东西”,所以不存在派生于何处的问题。Anders在《C# Programming Language 》中说,接口只是表示一种约定[contract]); 委托:delegate(派生于System.Delegate)。 object(System.Object的别名); 字符串:string(System.String的别名)。

可以看出:

引用类型与值类型相同的是,结构体也可以实现接口; 引用类型可以派生出新的类型,而值类型不能; 引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型); 引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值。

对于最后一条,经常混淆的是string。我曾经在一本书 的一个早期版本上看到String变量比string变量效率高;我还经常听说String是引用类型,string是值类型,等等。例如:

string s1 = " Hello, " ;

string s2 = " world! " ;

string s3 = s1 + s2; // s3is"Hello,world!"

这确实看起来像一个值类型的赋值。再如:

string s1 = " a " ;

string s2 = s1 ;

s1 = "b " ; // s2is still"a"

改变s1的值对s2没有影响。这更使string看起来像值类型。实际上,这是运算符重载的结果,当s1被改变时,.NET在托管堆上为s1重新分配了内存。这样的目的,是为了将做为引用类型的string实现为通常语义下的字符串。

4. 值类型和引用类型在内存中的部署

经常听说,并且经常在书上看到:值类型部署在栈上,引用类型部署在托管堆上。实际上并没有这么简单。

MSDN上说 :托管堆上部署了所有引用类型。这很容易理解。当创建一个应用类型变量时:

object reference = new object ();

关键字new将在托管堆上分配内存空间,并返回一个该内存空间的地址。左边的reference位于栈上,是一个引用,存储着一个内存地址;而这个 地址指向的内存(位于托管堆)里存储着其内容(一个System.Object的实例)。下面为了方便,简称引用类型部署在托管推上。

再来看值类型。《C#语言规范 》 上的措辞是“结构体不要求在堆上分配内存(However, unlike classes, structs are value types and do not require heap allocation)”而不是“结构体在栈上分配内存”。这不免容易让人感到困惑:值类型究竟部署在什么地方?

4.1 数组

考虑数组:

int []reference = new int [ 100 ];

根据定义,数组都是引用类型,所以int数组当然是引用类型(即reference.GetType().IsValueType为false)。

而int数组的元素都是int,根据定义,int是值类型(即reference[i].GetType().IsValueType为true)。那么引用类型数组中的值类型元素究竟位于栈还是堆?

如果用WinDbg去看reference[i]在内存中的具体位置 ,就会发现它们并不在栈上,而是在托管堆上。

实际上,对于数组:

TestType[]testTypes = new TestType[ 100 ];

如果TestType是值类型,则会一次在托管堆上为100个值类型的元素分配存储空间,并自动初始化这100个元素,将这100个元素存储到这块内存里。

如果TestType是引用类型,则会先在托管堆为testTypes分配一次空间,并且这时不会自动初始化任何元素(即testTypes[i]均为null)。等到以后有代码初始化某个元素的时候,这个引用类型元素的存储空间才会被分配在托管堆上。

4.2 类型嵌套

更容易让人困惑的是引用类型包含值类型,以及值类型包含引用类型的情况:

public class ReferenceTypeClass

{

private int _valueTypeField;

public ReferenceTypeClass()

{

_valueTypeField = 0 ;

}

public void Method()

{

int valueTypeLocalVariable = 0 ;

}

}

ReferenceTypeClassreferenceTypeClassInstance = new ReferenceTypeClass(); // Whereis_valueTypeField?

referenceTypeClassInstance.Method(); // WhereisvalueTypeLocalVariable?

public struct ValueTypeStruct

{

private object _referenceTypeField;

public void Method()

{

_referenceTypeField = new object ();

object referenceTypeLocalVariable = new object ();

}

}

ValueTypeStructvalueTypeStructInstance = new ValueTypeStruct();

valueTypeStructInstance.Method(); // Whereis_referenceTypeField?AndwhereisreferenceTypeLocalVariable?

单看valueTypeStructInstance,这是一个结构体实例,感觉似乎是整块扔到栈上的。但是字段_referenceTypeField是引用类型,局部变量referenceTypeLocalVarible也是引用类型。

referenceTypeClassInstance也有同样的问题,referenceTypeClassInstance本身是引用类型,似 乎应该整块部署在托管堆上。但字段_valueTypeField是值类型,局部变量valueTypeLocalVariable也是值类型,它们究竟 是在栈上还是在托管堆上?

规律是:

引用类型部署在托管堆上; 值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实例)存储;作为局部变量时,存储在栈上。

我们来分析一下上面的代码。对于引用类型实例,即referenceTypeClassInstance:

从上下文看,referenceTypeClassInstance是一个局部变量,所以部署在托管堆上,并被栈上的一个引用所持有; 值类型字段_valueTypeField属于引用类型实例referenceTypeClassInstance的一部分,所以跟随引用类型实例referenceTypeClassInstance部署在托管堆上(有点类似于数组的情形); valueTypeLocalVariable是值类型局部变量,所以部署在栈上。

而对于值类型实例,即valueTypeStruct:

根据上下文,值类型实例valueTypeStructInstance本身是一个局部变量而不是字段,所以位于栈上; 其引用类型字段_referenceTypeField不存在跟随的问题,必然部署在托管堆上,并被一个引用所持有(该引用是valueTypeStruct的一部分,位于栈); 其引用类型局部变量referenceTypeLocalVariable显然部署在托管堆上,并被一个位于栈的引用所持有。

所以,简单地说“值类型存储在栈上,引用类型存储在托管堆上”是不对的。必须具体情况具体分析。

5. 正确使用值类型和引用类型

这一部分主要参考《Effective C# 》,并非本人原创,希望能让你加深对值类型和引用类型的理解。

5.1 辨明值类型和引用类型的使用场合

C#中,我们用struct/class来声明一个类型为值类型/引用类型。

考虑下面的例子:

TestType[] testTypes = new TestType[ 100 ];

如果TestTye是值类型,则只需要一次分配,大小为TestTye的100倍。而如果TestTye是引用类型,刚开始需要100次分配,分配 后数组的各元素值为null,然后再初始化100个元素,结果总共需要进行101次分配。这将消耗更多的时间,造成更多的内存碎片。所以,如果类型的职责 主要是存储数据,值类型比较合适。

一般来说,值类型(不支持多态)适合存储供 C#应用程序操作的数据,而引用类型(支持多态)应该用于定义应用程序的行为。

通常我们创建的引用类型总是多于值类型。如果以下问题的回答都为yes,那么我们就应该创建为值类型:

该类型的主要职责是否用于数据存储? 该类型的共有借口是否完全由一些数据成员存取属性定义? 是否确信该类型永远不可能有子类? 是否确信该类型永远不可能具有多态行为?

5.2 将值类型尽可能实现为具有常量性和原子性的类型

具有常量性的类型很简单:

如果构造的时候验证了参数的有效性,之后就一直有效; 省去了许多错误检查,因为禁止更改; 确保线程安全,因为多个reader访问到同样的内容; 可以安全地暴露给外界,因为调用者不能更改对象的内部状态。

具有原子性的类型都是单一的实体,我们通常会直接替换一个原子类型的整个内容。

下面是一个典型的可变类型:

public struct Address

{

private string _city;

private string _province;

private int _zipCode;

public string City

{

get { return _city;}

set {_city = value;}

}

public string Province

{

get { return _province;}

set

{

ValidateProvince(value);

_province = value;

}

}

public int ZipCode

{

get { return _zipCode;}

set

{

ValidateZipCode(value);

_zipCode = value;

}

}

}

下面创建一个实例:

Addressaddress = new Address();

address.City = "Chengdu " ;

address.Province = "Sichuan " ;

address.ZipCode = 610000 ;

然后更改这个实例:

address.City = "Nanjing " ; // NowProvinceandZipCodeareinvalid

address.ZipCode = 210000 ; // NowProvinceisstillinvalid

address.Province = "Jiangsu " ;

可见,内部状态的改变意味着可能违反对象的不变式(invariant),至少是临时的违反。如果上面是一个多线程的程序,那么在 City更改的过程中,另一个线程可能看到不一致的数据视图。如果不是多线程的程序,也有问题:

当ZipCode的值无效而抛出异常时,对象仅作了一部分改变,因此处于无效的状态,为了修复这个问题,需要在Address中添加相当多的内部校验代码; 为了实现异常安全,我们需要在所有改变多个字段的客户代码处放上防御性的代码; 线程安全也要求我们在每一个属性的访问器上添加线程同步检查。

显然,这是一个相当可观的工作量。下面我们把Address实现为常量类型:

public struct Address

{

private string _city;

private string _province;

private int _zipCode;

public Address ( string city, string province, int zipCode)

{

_city = city;

_province = province;

_zipCode = zipCode;

ValidateProvince(province);

ValidateZipCode(zipCode);

}

public string City

{

get { return _city;}

}

public string Province

{

get { return _province;}

}

public int ZipCode

{

get { return _zipCode;}

}

}

如果要改变Address,不能修改现有的实例,只能创建一个新的实例:

Addressaddress = new Address( "Chengdu " , "Sichuan " , 610000 ); // createainstance

address = new Address( "Nanjing " , "Jiangsu " ,210000 ); // modifytheinstance

address将不存在任何无效的临时状态。那些临时状态只存在于Address的构造函数执行过程中。这样一来,Address是异常安全的,也是线程安全的。

5.3 确保0为值类型的有效状态

.NET的默认初始化机制会将引用类型设置为二进制意义上的0,即null。而对于值类型,不论我们是否提供构造函数,都会有一个默认的构造函数,将其设置为0。

一种典型的情况是枚举:

public enum Sex

{

Male = 1 ;

Female = 2 ;

}

然后用做值类型的成员:

public struct Employee

{

private Sex_sex;

// other

}

创建Employee结构体将得到一个无效的Sex字段:

Employeeemployee = new Employee ();

employee的_sex是无效的,因为其为0。我们应该将0作为一个为初始化的值明确表示出来:

public Sex

{

None = 0 ;

Male = 1 ;

Female = 2 ;

}

如果值类型中包含引用类型,会出现另一种初始化问题:

public struct ErrorLog

{

private string _message;

// other

}

然后创建一个ErrorLog:

ErrorLogerrorLog = new ErrorLog ();

errorLog的_message字段将是一个空引用。我们应该通过一个属性来将_message暴露给客户代码,从而使该问题限定在ErrorLog 的内部:

public struct ErrorLog

{

private string _message;

public string Message

{

get

{

return (_message ! = null ) ? _message: string .Empty;

}

set {_message = value;}

}

// other

}

5.4 尽量减少装箱和拆箱

装箱指把一个值类型放入一个未具名类型的引用类型中,比如:

int valueType = 0 ;

object referenceType = i; // boxing

拆箱则是从前面的装箱对象中取出值类型:

object referenceType;

int valueType = ( int )referenceType; // unboxing

装箱和拆箱是比较耗费性能的,还会引入一些诡异的bug,我们应当避免装箱和拆箱。

装箱和拆箱最大的问题是会自动发生。比如:

Console.WriteLine( " Afewnumbers:{0},{1}. " , 25 , 32 );

其中,Console.WriteLine()接收的参数类型是(string,object,object)。因此,实际上会执行以下操作:

int i = 25 ;

obejecto = i; // boxing

然后把o传给WriteLine()方法。在WriteLine()方法的内部,为了调用i上的ToString()方法,又会执行:

int i = ( int )o; // unboxing

string output = i,ToString();

所以正确的做法应该是:

Console.WriteLine( " Afewnumbers:{0},{1}. " , 25 .ToString(), 32 .ToString());

25.ToString()只是执行一个方法并返回一个引用类型,不存在装箱/拆箱的问题。

另一个典型的例子是ArryList的使用:

public struct Employee

{

private string _name;

public Employee( string name)

{

_name = name;

}

public string Name

{

get { return _name;}

set {_name = value;}

}

public override string ToString()

{

return _name;

}

}

ArrayListemployees = new ArrayList();

employees.Add( new Employee( " OldName " )); // boxing

Employeeceo = (Employee)employees[ 0 ]; // unboxing

ceo.Name = " NewName " ; // employees[0].ToString()isstill"OldName"

上面的代码不仅存在性能的问题,还容易导致错误发生。

在这种情况下,更好的做法是使用泛型集合:

List < Employee > employees = new List < Employee > ();

由于List<T>是强类型的集合,employees.Add()方法不进行类型转换,所以不存在装箱/拆箱的问题。

6. 总结

C#中,变量是值还是引用仅取决于其数据类型。

C#的值类型包括:结构体(数值类型,bool型,用户定义的结构体),枚举,可空类型。

C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。

数组的元素,不管是引用类型还是值类型,都存储在托管堆上。

引用类型在栈中存储一个引用,其实际的存储位置位于托管堆。为了方便,本文简称引用类型部署在托管推上。

值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实例)存储;作为局部变量时,存储在栈上。

值类型在内存管理方面具有更好的效率,并且不支持多态,适合用作存储数据的载体;引用类型支持多态,适合用于定义应用程序的行为。

应该尽可能地将值类型实现为具有常量性和原子性的类型。

应该尽可能地确保0为值类型的有效状态。

应该尽可能地减少装箱和拆箱。

7. 参考

Effective C# Professional C# Programming .NET Components C#语言规范 Type Fundamentals Posted on -03-03 13:34 Dixin 阅读(5341) 评论(51) 编辑 收藏 所属分类: C#

Feedback

#1楼 回复 引用

-03-03 14:10 by xx_[未注册用户] 好长啊 有时间慢慢看

#2楼 回复 引用 查看

-03-03 14:15 by jillzhang 收藏,晚上看

#3楼 回复 引用

-03-03 14:44 by 柯[未注册用户] 支持一下。

#4楼 回复 引用

-03-03 14:44 by calem[未注册用户] 看了你的文章,我觉得好长见识!!

#5楼 回复 引用

-03-03 14:46 by zzyy[未注册用户] 支持

#6楼 回复 引用 查看

-03-03 15:01 by Justin 很长、很工整、很用心!很支持!呵呵

#7楼 回复 引用

-03-03 15:36 by aceting[未注册用户] 很全很深刻

#8楼 回复 引用

-03-03 15:40 by 小雨点[未注册用户] 终于明白了~~~

#9楼 回复 引用

-03-03 15:41 by chaochao[未注册用户] 专业!

#10楼 回复 引用

-03-03 15:43 by albertfay[未注册用户] 竞无语凝噎。。。

#11楼 回复 引用

-03-03 15:47 by KerU[未注册用户] 收藏了,回头看

#12楼 回复 引用 查看

-03-03 15:47 by 王孟军! 很好

#13楼 回复 引用

-03-03 15:53 by michaelkira[未注册用户] 细节决定成败啊

#14楼 回复 引用 查看

-03-03 15:55 by Zhuang miao 头像和和谐~

#15楼 回复 引用 查看

-03-03 16:06 by 蜗牛赛跑 就一個字

#16楼 [楼主 ] 回复 引用 查看

-03-03 16:19 by 笼民 @xx_

@柯

@calem

@zzyy

@aceting

@小雨点

@chaochao

@albertfay

@KerU

@michaelkira

谢谢支持!

#17楼 [楼主 ] 回复 引用 查看

-03-03 16:20 by 笼民 @jillzhang

@Justin

@王孟军!

@Zhuang miao

@蜗牛赛跑

谢谢支持!

#18楼 回复 引用

-03-03 16:46 by yfang[未注册用户] 牛人啊~~

#19楼 回复 引用 查看

-03-03 17:11 by 怪虎 说的非常好,让我有了一个深入的了解

#20楼 回复 引用 查看

-03-03 17:17 by Nove mark

#21楼 回复 引用 查看

-03-03 18:22 by 梦里花落知多少 讲得很透彻,让我领悟了很多

#22楼 回复 引用

-03-03 19:19 by kisfs[未注册用户] 好长的文章,慢慢看!

#23楼 回复 引用

-03-03 22:17 by RZ[未注册用户] Effective C#》也提及了关于何时使用引用类型和值类型的问题。有一点我希望请教楼主。

比如说,我们现在有一个class

public class DataType

{

private string _data = string.Empty;

public string Data

{

set {_data = value;}

get {return _data;}

}

}

好,我们在另一个class中去使用上边的class

public class Run

{

private DataType dataType = new DataType();

... ...

public DataType Foo()

{

return dataType;

}

}

依据面向对象的理论,上边的代码是有问题的。因为我们把一个私有的数据成员轻易地暴露出去了。但是,在实际开发中,我们经常会遇到上面类似的问题。那么是不是应该把DataType定义为一个struct呢?

但是,如果说我们将DataType定义为一个值类型,但是在若干年后,DataType如果涉及到多态,那么对于整个系统来说改动太大了。

这个问题一直困扰着我,希望能点播一二。

#24楼 回复 引用 查看

-03-03 23:18 by TerryLee 写得很仔细啊:)

#25楼 回复 引用 查看

-03-03 23:31 by 林肯 @RZ

这怎么算暴露私有的数据成员呢?

#26楼 回复 引用 查看

-03-04 01:48 by fox23 不错,相当的详实

#27楼 [楼主 ] 回复 引用 查看

-03-04 03:31 by 笼民 @RZ

你的意思我理解,因为我平时也会碰到类似的情况。比如这样一个类型:

class Data {

private Hashtable _field;

public Data() {

_field = new Hashtable();

_field["test"] = "old";

}

public Hashtable Property {

set {

_field = value;

}

get {

return _field;//Here is the problem.

}

}

}

然后使用这个类型:

class Program {

static void Main(string[] args) {

Data data = new Data();

Console.WriteLine(data.Property["test"]);//_field["test"] is "old".

Hashtable reference = data.Property;//_field is exposed.

reference["test"] = "new";//_field is also changed!

Console.WriteLine(data.Property["test"]);//_field["test"] is "new".

Console.Read();

}

}

这就是你说的把一个私有的数据成员(_field)轻易地暴露出去了。

我也没有比较完善的解决方案。一个简单的办法是,暴露私有数据成员的副本,而不是该成员本身。比如,将

get {

return _field;//Here is the problem.

}

改为

get {

return _field.Clone() as HashTable;//Here is the problem.

}

这样,就不会有这样的问题。Clone()方法来自ICloneable接口。

这样做的问题是,Clone()方法可能实现为deep copy,也可能是shallow copy。使用时应特别小心。比如,调用DataSet的Clone()方法,只会拷贝数据结构,要连同数据一起拷贝,要调用Copy()方法。

本人才疏学浅,大家有没有更好的办法?

#28楼 [楼主 ] 回复 引用 查看

-03-04 03:40 by 笼民 @RZ

但是你举的例子恰好有一定的特殊性。在你的例子里,真正的数据是被string型字段持有。比如:

class Data {

private string _field = "old";

public string Property {

set {

_field = value;

}

get {

return _field == null ? string.Empty : _field;

}

}

}

然后:

class Program {

static void Main(string[] args) {

Data data = new Data();

Console.WriteLine(data.Property);//_field is "old".

string reference = data.Property;//Attention! _field and reference are "old".

reference = "new";//_field is not changed.

Console.WriteLine(data.Property);//_field is "old".

Console.Read();

}

}

原因就是我文章里提到的:string reference = data.Property这种赋值看起来就像是值类型的赋值。

#29楼 回复 引用

-03-04 09:54 by 天堂落日[未注册用户] 4.2的例子有待商榷:

结构不能包含显式的无参数构造函数

#30楼 [楼主 ] 回复 引用 查看

-03-04 10:05 by 笼民 @天堂落日

大意了。。。已改正,多谢!

#31楼 回复 引用 查看

-03-04 11:28 by BlueMountain 4.1 数组

TestType[] testTypes = new TestType[100];

如果TestType是值类型,则会一次在托管堆上为100个值类型的元素分配存储空间,并自动初始化这100个元素,将这100个元素存储到这块内存里。

如果TestType是引用类型,则会先在托管堆为testTypes分配一次空间,并且这时不会自动初始化任何元素(即testTypes[i]均为null)。等到以后有代码初始化某个元素的时候,这个引用类型元素的存储空间才会被分配在托管堆上。

----------------------------------------------------------------

5. 正确使用值类型和引用类型

TestType[] testTypes = new TestType[100];

如果TestTye是值类型,则只需要一次分配,大小为TestTye的100倍。而如果TestTye是引用类型,刚开始需要100次分配,分 配后数组的各元素值为null,然后再初始化100个元素,结果总共需要进行101次分配。这将消耗更多的时间,造成更多的内存碎片。所以,如果类型的职 责主要是存储数据,值类型比较合适。

---------------------------------------------------------------

lz 4,5 说得有些矛盾,能够把这点讲得清楚一些,多谢。(就是引用类型数组)

#32楼 [楼主 ] 回复 引用 查看

-03-04 13:07 by 笼民 我感觉没有矛盾啊

#33楼 [楼主 ] 回复 引用 查看

-03-04 13:11 by 笼民 class Program{

static void Main( string []args){

int []valueTypeArray = new int [ 100 ]; // 注意new,valueTypeArray的100个成员一次性初始化为0(即int的默认值),即所需内存1次分配好了

Console.WriteLine(valueTypeArray[ 10 ].ToString()); // 输出0

Hashtable[]referenceTypeArray = new Hashtable[ 100 ]; // 注意new,这时为referenceTypeArray分配内存

Console.WriteLine((referenceTypeArray[ 10 ] == null ).ToString()); // 但是referenceTypeArray的成员并没有被初始化,referenceTypeArray[10]为null,输出True

for ( int i = 0 ;i < 100 ;i ++ ){

referenceTypeArray[i] = new Hashtable(); // 注意new,要初始化100个成员,我们要分配100次内存

}

// 所以一共是101次

Console.Read();

}

}

#34楼 回复 引用

-03-04 15:31 by qb2xxx[未注册用户] 楼主说得挺好的。回答别人的问题也挺有耐心。顶一个。

#35楼 回复 引用

-03-04 16:17 by 路人A[未注册用户] 好文章啊。

学到了很多。

#36楼 [楼主 ] 回复 引用 查看

-03-04 16:39 by 笼民 @路人A

@qb2xxx

@RZ

@kisfs

谢谢支持!

#37楼 [楼主 ] 回复 引用 查看

-03-04 16:43 by 笼民 @BlueMountain

@梦里花落知多少

@fox23

@TerryLee

@Nove

@怪虎

谢谢支持!

#38楼 回复 引用 查看

-03-05 08:51 by zeus2 博主如果精通C++就知道了。

我的理解,C#的引用就是指指针。

比如int[] p = new int[10];这句话等于

int * p = new int[10];肯定分配在堆上。

而如string[] s = new string[10]这类的就是分配一个指针数组。

#39楼 回复 引用

-03-05 09:03 by GPS监控[未注册用户] 博主有才

#40楼 [楼主 ] 回复 引用 查看

-03-05 11:41 by 笼民 @zeus2

和C++不一样,具体我晚上再来写。

#41楼 回复 引用

-03-05 12:36 by server[未注册用户] @笼民

Jeffrey Richter的那本书你看也不止三遍吧:)

觉得你要是画出图能更清晰,调用实例方法和静态方法的你没有涉及到,还要说一下方法表指针,同步索引块

而关于引用类型,你要说的更狠一点才行,因为封装、继承,多态主要就是围绕引用类型来的,我是写不好,呵呵,但实际上一个helloworld程序说清楚不是也能讲个几天几夜吗

#42楼 [楼主 ] 回复 引用 查看

-03-05 12:45 by 笼民 @server

那本书我断断续续看过一些。。。

我是一个UI designer,能花在技术上的时间非常有限,有机会我一定写:)

谢谢你的建议!

#43楼 回复 引用

-03-06 08:36 by Wey[未注册用户] 有两个类A和B,其中B继承于A,为什么A a=new B() ; a.GetType() 会是B 呢?能解释一下吗?

#44楼 [楼主 ] 回复 引用 查看

-03-06 10:07 by 笼民 @Wey

你说的这个叫多态(Polymorphism)

简单地说,你把a想象成一个指针,指向一个B的实例,调用a.GetType(),调用的是a指向的实例上的GetType()方法。

#45楼 [楼主 ] 回复 引用 查看

-03-06 18:45 by 笼民 @zeus2

我感觉这个很难简单地讲清楚。

C#中变量是值还是引用仅取决于其数据类型。C++要复杂得多。

#46楼 回复 引用

-07-03 14:00 by 赵晓伟[未注册用户] 赵晓伟到此一游

程序我不行,

#47楼 回复 引用

-07-13 17:07 by 红宇[未注册用户] 我是名新手,看了你写的这篇文章收获不少,写得很通透。感谢您分享!

#48楼 回复 引用

-08-01 12:04 by 蒋能忠[未注册用户] 很好!!

#49楼 回复 引用

-08-04 17:31 by gosling[未注册用户] 楼主能这么详细的讲解。。支持。。。望能结交朋友,,还有很多题想请教。。

404886498

#50楼 回复 引用 查看

-12-01 22:50 by 右手年华 谢楼主,收藏了

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。