为什么c#string c 中string是引用类型型

博客访问: 7052115
博文数量: 1489
博客积分: 13501
博客等级: 上将
技术积分: 17808
注册时间:
APP发帖 享双倍积分
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
发布时间: 14:24:41
1、string的确是引用类型
常规上是这样定义的:
string是引用类型,但是其又具有值类型的一些特性。
由图1可以总结出下面几点:
(1)引用给引用赋值,是将两个引入都同指向同一片内存,并没有开发新的内存。
str2=str1,这样大家都指向了”I am a number”所在的内存,并没有新开辟一片内存。
(2).........
阅读(1976) | 评论(0) | 转发(0)
发布时间: 11:47:42
【CSharp代码1】:
string s0 = "";
string s1 =
对应的反汇编代码:
&&&&&&&&&&& string s0 = "";
& mov&&&&&&&& eax,dword ptr ds:[0232202Ch]
0000004f& mov&&&.........
阅读(1300) | 评论(0) | 转发(0)
发布时间: 10:46:31
在C#中,string 是 System.String 的别名,所以基本上在使用时是没有差别的。
1、习惯上,我们把字符串当作对象时(有值的对象实体),我们用string。而我们把它当类时(需要字符串类中定义的方法),我们用String,比如:
string greet = String.Format("Hello {0}!", place);
2、string是C#保留字,不可用作变量名.........
阅读(1440) | 评论(0) | 转发(0)
发布时间: 10:03:03
一、案例代码
二、主要关注点:
1、String构造函数中的Unicode字符的写法:
‘\u0319’,而不是’/u0319’;
2、String的三个构造函数的写法:
new String('\u0319', 5);
创建5个’\u0319’到字符串;
new String(new char[] { '\u03A9', '\u03A9', '\u03A9' });
将 System.String 类的新实例初始化为由 Unicode .........
阅读(1394) | 评论(0) | 转发(0)
发布时间: 17:45:05
AutoScaleMode 枚举
指定 Windows 窗体支持的不同类型的自动缩放模式。
图1 form1初始设计界面
1、添加到panel的form1中不能正常显示的设置方式:
autoScaleMode设置为forn,显示效果如图3,有一个son1按钮已经出现了严重的偏离!!!!
panel中添加了Form1后的显示效果.
阅读(1292) | 评论(0) | 转发(0)
给主人留下些什么吧!~~
meng:有好多我要学习的东西啊、】
华冠一锅3:博客不错,交个朋友
博客不错,交个朋友
有好多我要学习的东西啊、】
请登录后留言。&&& &要了解一门编程语言,首先就要了解它的类型。我们知道,C#一共分为两大类型:值类型和引用类型,但值类型并不单纯是我们java中的基本数据类型那么简单,有关于是否使用值类型还是个值得讨论的问题:因为装箱机制。C#的值类型还能够自定义方法,甚至能够实现引用类型的接口类型!这已经超出了我的想象范围了!
&&& 先来点基础的东西:
&&&& 文档是我们学习的好帮手,在C#的文档中,我们必须注意,凡是引用类型的,名字都是"xx类",凡是值类型的,就叫"xx结构"或"xx枚举"。
&&&& 很多时候,我们的初始化操作的右值是表达式。如果左值是值类型,那么它的值就是表达式的值,但如果是引用类型,则是一个引用,并不是该引用指向的对象(学过java,所以对这现象非常熟悉),所以,在C#中,String.Empty的值不是一个空字符串,而是对空字符串的引用。
&&&& 声明一个变量,除了它的类型信息外,最重要的就是值的存储地点。变量的值一般是在它声明时的位置存储的,而实例变量的值存储在实例本身存储的地方,引用类型和静态变量则存储在堆中。
&&&& 也许我们会以为,值类型一定在栈(stack,有些地方叫线程栈)上,引用类型一定在堆(heap,有些地方叫托管堆)上,其实这是错误的,值类型也可以在堆上,像是前面讲过的,变量的值存储在它声明的地方,如果我的值类型是在一个引用类型中声明的,那么该值类型就是在堆上。方法参数一定是在栈(方法是用栈帧存储它的信息)上,但局部变量不一定,它也可以是在堆上(让我们想想匿名方法,如果它捕获了一个外部变量,该变量就会作为隐藏委托类型而保存在堆上)。好了,一个潜在的想法出现了:如果一个引用类型保存在值类型中,像是结构中,会怎么样呢?引用类型的数据依然保存在堆中,但它的引用保存在栈上,因为值类型也只拥有它的引用而已。
&&&& 值类型不能派生出其他类型,因为它是隐式密封(sealed)的,所以它并没有引用类型实例对象开头的额外信息(用于标识对象的实际类型和其他信息),即类型对象指针和同步块索引。这些额外信息并不能修改,所以我们永远也不能修改对象的类型,但我们可以转换为其他类型。执行强制类型转换时,我们会获取一个引用,该引用会检查它引用的对象本身是否是该目标类型的有效对象,若是,返回该引用并赋给目标类型,否则抛出异常。引用并不清楚它实际引用的对象的类型,所以我们的引用可以指向其他类型。
&&&&基础的东西讲完后,就应该讲讲一些不一样的东西(虽然大部分很多人都已经非常熟悉了)。细节不讨论,只是单单从几个话题出发并做一下延伸。
话题1:结构是轻量级的类
&&&& &结构虽然是值类型,但它可以定义属性和方法,类的行为它都具备,加上它是值类型,比起引用类型来说,对内存更加友好。所以,就有一种说法:结构是轻量级的类。这种说法见仁见智,值类型的确是"轻":不需要垃圾回收(不在堆上分配),不会因类型标识而产生开销(没有类型对象指针和同步块索引),而且不需要取值这一步操作(引用类型的字段一般都是私有的,我们只能通过访问器getter取得其值)。但引用类型也有它的好处:在执行传参,赋值,返回值等类似操作时,只需要赋值4或8字节(具体得看CLR),因为我们传递的只是一个引用(我想说指针,但又觉得不合适,从来就没有任何一种说法认为引用就等同于指针,虽然CLR的引用的确是一个地址,但CLR强调,它们是引用),而不是复制所有数据。这点在容器那里非常好用,像是List,如果容量很大,那这个传参动作就太可怕了。
&&& 值类型也并不总是对内存友好,因为隐式的装箱机制,会使某些情况像是循环等,使用值类型会造成可怕的负担。C#中的值类型使用起来并不像java的基本数据类型那么简单(java并没有隐式的装箱机制,基本数据类型不可能直接转换为类),除非是以下几种情况,我们才能放心使用值类型:
&&& 1.值类型是不可变(immutable)的,即该类型没有提供任何方法来修改它的字段。要做到这点,我们必须将值类型的所有字段都设置为readonly(只读)。这主要针对结构这种封装其他值类型的值类型。
&& 2.类型的实例较小(小于16字节),因为按值传递需要复制字段,但类型实例较大以致不可能作为实参传递,也不可能作为返回值的话,也可以考虑使用值类型。
&&&就算满足上面的条件,我们也必须考虑到值类型的缺点(装箱机制不在列表中,因为这个话题前面已多次提醒了):
&& 1.值类型继承自System.ValueType,该类除了提供与System.Object一样的方法外,还做了一个动作:覆写了Equals()方法和GetHashCode()方法(值类型的比较需要考虑到它的字段,但默认的比较是引用),而默认的实现存在性能问题。我们不能苛求设计者能够考虑到所有情况,所以,大部分情况都要我们自己覆写这两个方法(这个问题不知道是不是从java而来,java也存在这样的问题)。
&& 2.值类型可以有自己的方法,但它不能派生也不能继承(虽然能实现接口),因此它不能含有虚方法,所有方法也是不可覆写的。
&& 3.引用类型的默认值是null,但我们引用一个null的引用类型时会抛出异常:NullReferenceException,但值类型默认值是0,并不会抛出异常。CLR为了弥补这点,提供了可空性(nullability)标识---可空类型(nullable)。
&& 4.值类型之间的相互赋值,会导致字段的复制,但引用类型只是复制引用。
&&5.值类型并不在堆上分配,所以当它被销毁时,不会通过Finalize方法接到一个通知(这点在有些地方很重要,这时就需要装箱)。
& 看了以上的讨论,相信对使用值类型是有点怕怕的:自己是否用错了呢?程序员是不需要顾虑那么多的,写代码最主要是能够表达清楚自己的意图,至于性能这方面,是可以在后期进行重构和优化的。
话题2:对象在C#中默认是通过引用传递
&&&& 引用传递(pass by reference)的定义非常复杂,百度百科的解释是这样的:可以将一个变量通过引用传递给函数,这样该函数就可以修改其参数的值,而引用的解释就是某一变量的一个别名,对引用的操作与对变量直接操作完全一样(很多人都说java是按引用传递,其实这种说法是不严谨的,严格意义上是传递引用对象地址值的按值传递)。如果我们以按引用传递的方式传递变量,那么调用的方法可以通过更改其参数值,来改变调用者的变量值。但C#中引用类型变量的值虽然叫引用,但不是对象本身,它更接近于指针。
&&&&& 就算是传参,有些情况也不能修改它的值:
class Program
public static void Main(String[] args)
String builder = "hello";
Show(builder);
Console.WriteLine(builder);
static void Show(String str)
str = "word";
Console.WriteLine(str);
&&&&&&&在Show()方法里,修改的只是builder的一个副本。当然,String虽然是引用类型,但它是不可变的。我们来传一个真正的引用类型:
class Program
public static void Main(String[] args)
People people = new People();
Show(people);
Console.WriteLine(people.name);
static void Show(People people)
people.name = "男人";
public class People
public String name = "人";
&&&&&&&&这里我们看到,字段name改变了。
&&&&&& 很困惑吧?为什么还说C#是按值传递呢?C#中,引用类型作为方法参数确实是按"值"传递,因为引用类型的值是引用,而该引用是一个地址值,相当于指针(只是相当于,并不等于)。真正的引用传递的就是对象本身,因为引用本身就是对象的别名,但C#是不会传递对象本身的。
&&&&& 这个问题非常让人纠结,尤其是CLR采取了引用这个说法,使得我们更加困扰了。
&&&&&&&C#的值类型非常奇怪,我们甚至可以用new来声明:
int number = new int();
number = 5;
&&&&& 这并没有错,但刚从java中跳出来的我非常惊讶!
&&&&&C#编译器是很聪明的,它知道number是一个值类型,因为它并没有类型对象指针,于是在栈上为它分配内存,然后确保所有字段都初始化为0。这样的动作就算不用new也行:
&&& 但是用new,编译器就认为该实例已经初始化了,而上面的情况如果我们为它赋值就会发生错误。所以,声明一个值类型最好就是为它进行初始化,哪怕只是默认值。
&&& 关于这方面的讨论,很多时候我都有心无力,毕竟自己这个初学者要想啃下CLR,难度很大,有什么不对的地方还请见谅。
阅读(...) 评论()C#中内置的引用类型:Object和string 有什么区别?-学网-中国IT综合门户网站-提供健康,养生,留学,移民,创业,汽车等信息
> 信息中心 >
C#中内置的引用类型:Object和string 有什么区别?
来源:互联网 发表时间: 18:39:22 责任编辑:鲁晓倩字体:
为了帮助网友解决“C#中内置的引用类型:Object和string 有什么区别?”相关的问题,学网通过互联网对“C#中内置的引用类型:Object和string 有什么区别?”相关的解决方案进行了整理,用户详细问题包括:RT,我想知道:C#中内置的引用类型:Object和string 有什么区别?,具体解决方案如下:解决方案1:
OBJECT是引用类型,但如果是int之类则这个应用可能是值,因为int也是继承自objectstring在内存的表示上是引用(如果有C基础就能很好明白),但在实际使用中,.NET把string弄成了值类型。所以不要把string当引用类型看。.NET中把引用的字符串值类型化的方案是每次对字符串变量赋值,包括字符串相加都是完全开辟新的存储空间给字符串变量使用。所以在使用string类型进行运算时比较消耗资源,此时可以使用stringbuilder类型替代。
解决方案2:
object是对象,string是字符串
解决方案3:
Object是基类,包含所有类.String类也是包含于其中的,字符串类.
解决方案4:
string对象是继承Object的一个子类 Object是所有类的父类
4个回答6个回答1个回答5个回答2个回答3个回答2个回答3个回答2个回答1个回答1个回答1个回答1个回答1个回答1个回答1个回答1个回答1个回答
相关文章:
<a href="/cse/search?q=<inputclass="s-btn"type="submit"text="<inputclass="s-btn"type="submit"text="<buttonhidefocusclass="s-btnjs-ask-btn"text="我要提问
<a href="/cse/search?q=在C#中,为什么是String,它的行为就像一个值类型引用类型?-c#,string,clr,value-type,reference-type-CodeGo.net
在C#中,为什么是String,它的行为就像一个值类型引用类型?
字符串是即使它有最值类型的特性如被不可改变的,有==重载的文字,而不是确保他们引用的对象的引用类型。
为什么不只是字符串值类型呢?
本文地址 :CodeGo.net/37773/
-------------------------------------------------------------------------------------------------------------------------
1. 字符串是不是值类型,因为它们可以是巨大的,并且需要被存储在堆中。值类型(在所有的CLR尚未的)存储在堆栈上。堆栈分配的字符串将打破各种东西:堆栈只有1MB,你不得不框中的每个字符串,招致副本点球,你不能实习生字符串,将使用气球 CodeGo.net,等..
(编辑:。约值类型的存储作为一个细节,这导致了这种情况,我们有一个类型与值语义学不是从System.ValueType继承了补充解释由于本)
这不是一个值类型的性能(空间,将是可怕的,如果它是一个值类型,它的值完全有它传递给被复制到并返回等等。
它具有值语义保持的世界理智。你能想象这将是多么困难的是如果代码
string s = "hello";
string t = "hello";
bool b = (s == t);
集b要false?艰难可想而知编码几乎所有的应用程序将。
不仅字符串是不可变的引用类型。
多投的代表了。
这就是为什么它是安全的书写
protected void OnMyEventHandler()
delegate handler = this.MyEventH
if (null != handler)
handler(this, new EventArgs());
我想,字符串是不可变的,这是最与他们合作,并
为什么他们不是值类型?作者是对的堆栈大小等我还要补充一点,使字符串的引用类型允许以保存装配尺寸时,程序中的字符串常量。如果你定义
string s1 = "my string";
//some code here
string s2 = "my string";
机会是为“我的字符串”常量两个实例将被分配在您的程序集只有一次。
如果你想管理的字符串引用类型,将字符串中一个新的StringBuilder(字符串s)。 MemoryStreams。
如果你是建立一个图书馆,在那里你expect在你的函数传递一个巨大的字符串,要么定义为一个StringBuilder或流。
此外,该字符串的方法是(不同为每个平台),当你开始缝合在一起.aStringBuilder。它allocats为您复制到缓冲区中,一旦你到达终点时,即使分配给你,在希望,如果你做一个大的concatenation性能不会受到阻碍。
也许乔恩斯基特可以帮助了在这里?
它是主要的性能问题。
在编写代码时有串像值类型会有所帮助,但有它是值类型会产生巨大的性能损失。
如需深入了解,采取偷看在.net络上串一个不错的文章
其实弦很少有相似之处值类型。对于初学者来说,并不是所有的值类型是不可变的,你可以改变一个Int32所有你想要的值,并将它它仍然是在栈上的地址。
字符串是不可变的一个很好的理由,它无关,与它是一个引用类型,但有很多工作要做,这只是更有效地创建一个新的对象时,字符串大小的变化,而不是在托管堆上转向周围的事物。我认为你值/引用类型和不可变对象概念混合在一起。
至于“==”:就像你说的“==”是一个运算符重载,再次它是一个很好的理由处理字符串时进行。
是不是因为字符串是由字符数组一样简单。我看的字符串作为字符数组[]。因此,他们都在位置存储堆栈和指向堆上的数组&#39;的开始位置在堆上。该字符串的大小是不知道它是分配前...完美的堆。
这就是为什么一个字符串是真的不可改变当你改变它,即使它的大小并不知道这一点,必须分配一个新的数组并指定字符数组中的位置。这是有道理的,如果你认为字符串作为一种方式,语言的保护你不必上飞(读类似C编程)
你怎么能知道string是一个引用类型?我不知道它的问题是怎么回事字符串在C#中是不可变的正是这样你就不必担心这个问题。
引用类型和值类型之间的区别基本上都是在语言设计一个性能权衡。引用类型有开销的构造和析构和垃圾收集,他们是在堆上创建的。另一方面值类型具有开销呼叫(如果该数据尺寸小于一个指针更大),整个对象被复制,而不是仅仅一个指针。字符串可以是(并且通常是)比指针的大小大得多,它们被设计成引用类型。另外,作为Servy指出,值类型的大小必须是已知的,这并不总是为字符串的情况。
可变性的问题是一个单独的问题。这两种引用类型和值类型可以是可变的或者不可变的。值类型通常是不可变的,虽然,因为对于可变的值类型的语义可以
引用类型通常是可变的,但也可以设计成不可变的,如果它是有道理的。字符串被定义为不可变的它使某些优化成为可能。例如,如果字符串字面多发生在程序(这是可以的对象。
那么,为什么是“==”重载字符串由文本?它是语义。如果两个字符串相等的文字,他们可能会或可能不会是由于优化的对象引用。引用的文字几乎都是你想要的。
说到更一般地,字符串有什么是值语义。这是一个比较笼统的概念不是值类型,这是一个C#的具体细节。值类型具有值语义,但引用类型也可能有值语义。当一个类型具有值语义,你真的不能告诉如果标的是引用类型或值类型,所以你可以认为一个细节。
本文标题 :在C#中,为什么是String,它的行为就像一个值类型引用类型?
本文地址 :CodeGo.net/37773/
Copyright (C) 2014 CodeGo.netC#值类型和引用类型的深入理解
从概念上看,&#20540;类型直接存储其&#20540;,而引用类型存储对其&#20540;的引用。这两种类型存储在内存的不同地方。在C#中,我们必须在设计类型的时候就决定类型实例的行为。这种决定非常重要,用《CLR via C#》作者Jeffrey Richter的话来 说,“不理解引用类型和&#20540;类型区别的程序员将会给代码引入诡异的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.)”。这就要求我们正确理解和使用&#20540;类型和引用类型。
1. 通用类型系统
C#中,变量是&#20540;还是引用仅取决于其数据类型。
C#的基本数据类型都以平台无关的方式来定义。C#的预定义类型并没有内置于语言中,而是内置于.NET Framework中。.NET使用通用类型系统(CTS)定义了可以在中间语言(IL)中使用的预定义数据类型,所有面向.NET的语言都最终被编译为 IL,即编译为基于CTS类型的代码。
例如,在C#中声明一个int变量时,声明的实际上是CTS中System.Int32的一个实例。这具有重要的意义:
确保IL上的强制类型安全;&
实现了不同.NET语言的互操作性;&
所有的数据类型都是对象。它们可以有方法,属性,等。例如:&
s = i.ToString();
MSDN的这张图说明了CTS中各个类型是如何相关的。注意,类型的实例可以只是&#20540;类型或自描述类型,即使这些类型有子类别也是如此。
2. &#20540;类型
C#的所有&#20540;类型均隐式派生自System.ValueType:
结构体:struct(直接派生于System.ValueType);
C#的结构和类在使用上没有区别,唯一的区别是在内存里的储存方式。&
结构将会被执行库分配存在堆栈上,而类将会被执行库分配存储在引用托管堆中。&
也就是C#的结构是被执行库当作用户自定义的&#20540;类型对待的,而类是被当作引用类型对待。&
在需要访问快、且几乎不做运算的那些数据应该作为结构,而需要做很多运算的那些数据应该作为类。
数&#20540;类型:&
整 型: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&的别名)。
每种&#20540;类型均有一个隐式的默认构造函数来初始化该类型的默认&#20540;。例如:
int i = new int();
Int32 i = new Int32();
int i = 0;
Int32 i = 0;
使用new运算符时,将调用特定类型的默认构造函数并对变量赋以默认&#20540;。在上例中,默认构造函数将&#20540;0赋给了i。MSDN上有完整的默认&#20540;表。
关于int和Int32的细节,在我的另一篇文章中有详细解释:《理解C#中的System.Int32和int》。
所有的&#20540;类型都是密封(seal)的,所以无法派生出新的&#20540;类型。
&#20540;得注意的是,System.ValueType直接派生于System.Object。即System.ValueType本身是一个类类型,而 不是&#20540;类型。其关键在于ValueType重写了Equals()方法,从而对&#20540;类型按照实例的&#20540;来比较,而不是引用地址来比较。
可以用Type.IsValueType属性来判断一个类型是否为&#20540;类型:
TestType testType = new TestType ();
if (testTypetype.GetType().IsValueType)
Console.WriteLine(&{0} is value type.&, testType.ToString());
3. 引用类型
C#有以下一些引用类型:
数组(派生于System.Array)&
用户用定义的以下类型:&
类:class(派生于System.Object);&
接口:interface(接口不是一个“东西”,所以不存在派生于何处的问题。Anders在《C# Programming Language》中说,接口只是表示一种约定[contract]);&
委托:delegate(派生于System.Delegate)。&
object(System.Object的别名);&
字符串:string(System.String的别名)。
可以看出:
引用类型与&#20540;类型相同的是,结构体也可以实现接口;&
引用类型可以派生出新的类型,而&#20540;类型不能;&
引用类型可以包含null&#20540;,&#20540;类型不能(可空类型功能允许将 null 赋给&#20540;类型);&
引用类型变量的赋&#20540;只对对象的引用,而不对象本身。而将一个&#20540;类型变量赋给另一个&#20540;类型变量时,将包含的&#20540;。
对于最后一条,经常混淆的是string。我曾经在一本书的一个早期版本上看到String变量比string变量效率高;我还经常听说String是引用类型,string是&#20540;类型,等等。例如:
string s1 = &Hello, &;
string s2 = &world!&;
string s3 = s1 &#43; s2;//s3 is &Hello, world!&
这确实看起来像一个&#20540;类型的赋&#20540;。再如:
string s1 = &a&;
string s2 = s1;
s1 = &b&;//s2 is still &a&
改变s1的&#20540;对s2没有影响。这更使string看起来像&#20540;类型。实际上,这是运算符重载的结果,当s1被改变时,.NET在托管堆上为s1重新分配了内存。这样的目的,是为了将做为引用类型的string实现为通常语义下的字符串。
4. &#20540;类型和引用类型在内存中的部署
经常听说,并且经常在书上看到:&#20540;类型部署在栈上,引用类型部署在托管堆上。实际上并没有这么简单。
MSDN上说:托管堆上部署了所有引用类型。这很容易理解。当创建一个应用类型变量时:
object reference = new object();
关键字new将在托管堆上分配内存空间,并返回一个该内存空间的地址。左边的reference位于栈上,是一个引用,存储着一个内存地址;而这个 地址指向的内存(位于托管堆)里存储着其内容(一个System.Object的实例)。下面为了方便,简称引用类型部署在托管推上。
再来看&#20540;类型。《C#语言规范》 上的措辞是“结构体不要求在堆上分配内存(However, unlike classes, structs are value types and do not require heap allocation)”而不是“结构体在栈上分配内存”。这不免容易让人感到困惑:&#20540;类型究竟部署在什么地方?
考虑数组:
int[] reference = new int[100];
根据定义,数组都是引用类型,所以int数组当然是引用类型(即reference.GetType().IsValueType为false)。
而int数组的元素都是int,根据定义,int是&#20540;类型(即reference[i].GetType().IsValueType为true)。那么引用类型数组中的&#20540;类型元素究竟位于栈还是堆?
如果用WinDbg去看reference[i]在内存中的具体位置,就会发现它们并不在栈上,而是在托管堆上。
实际上,对于数组:
TestType[] testTypes = new TestType[100];
如果TestType是&#20540;类型,则会一次在托管堆上为100个&#20540;类型的元素分配存储空间,并自动初始化这100个元素,将这100个元素存储到这块内存里。
如果TestType是引用类型,则会先在托管堆为testTypes分配一次空间,并且这时不会自动初始化任何元素(即testTypes[i]均为null)。等到以后有代码初始化某个元素的时候,这个引用类型元素的存储空间才会被分配在托管堆上。
4.2 类型嵌套
更容易让人困惑的是引用类型包含&#20540;类型,以及&#20540;类型包含引用类型的情况:
public class ReferenceTypeClass
private int _valueTypeF
public ReferenceTypeClass()
_valueTypeField = 0;
public void Method()
int valueTypeLocalVariable = 0;
ReferenceTypeClass referenceTypeClassInstance = new ReferenceTypeClass();//Where is _valueTypeField?
referenceTypeClassInstance.Method();//Where is valueTypeLocalVariable?
public struct ValueTypeStruct
private object _referenceTypeF
public ValueTypeStruct()
_referenceTypeField = new object();
public void Method()
object referenceTypeLocalVariable = new object();
ValueTypeStruct valueTypeStructInstance = new ValueTypeStruct();//Where is _referenceTypeField?
valueTypeStructInstance.Method();//Where is referenceTypeLocalVariable?
单看valueTypeStructInstance,这是一个结构体实例,感觉&#20284;乎是整块扔到栈上的。但是字段_referenceTypeField是引用类型,局部变量referenceTypeLocalVarible也是引用类型。
referenceTypeClassInstance也有同样的问题,referenceTypeClassInstance本身是引用类型,&#20284; 乎应该整块部署在托管堆上。但字段_valueTypeField是&#20540;类型,局部变量valueTypeLocalVariable也是&#20540;类型,它们究竟 是在栈上还是在托管堆上?
引用类型部署在托管堆上;&
&#20540;类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实例)存储;作为局部变量时,存储在栈上。
我们来分析一下上面的代码。对于引用类型实例,即referenceTypeClassInstance:
从上下文看,referenceTypeClassInstance是一个局部变量,所以部署在托管堆上,并被栈上的一个引用所持有;&
&#20540;类型字段_valueTypeField属于引用类型实例referenceTypeClassInstance的一部分,所以跟随引用类型实例referenceTypeClassInstance部署在托管堆上(有点类&#20284;于数组的情形);&
valueTypeLocalVariable是&#20540;类型局部变量,所以部署在栈上。
而对于&#20540;类型实例,即valueTypeStruct:
根据上下文,&#20540;类型实例valueTypeStructInstance本身是一个局部变量而不是字段,所以位于栈上;&
其引用类型字段_referenceTypeField不存在跟随的问题,必然部署在托管堆上,并被一个引用所持有(该引用是valueTypeStruct的一部分,位于栈);&
其引用类型局部变量referenceTypeLocalVariable显然部署在托管堆上,并被一个位于栈的引用所持有。
所以,简单地说“&#20540;类型存储在栈上,引用类型存储在托管堆上”是不对的。必须具体情况具体分析。
5. 正确使用&#20540;类型和引用类型
这一部分主要参考《Effective C#》,并非本人原创,希望能让你加深对&#20540;类型和引用类型的理解。
5.1 辨明&#20540;类型和引用类型的使用场合
C#中,我们用struct/class来声明一个类型为&#20540;类型/引用类型。
考虑下面的例子:
TestType[] testTypes = new TestType[100];
如果TestTye是&#20540;类型,则只需要一次分配,大小为TestTye的100倍。而如果TestTye是引用类型,刚开始需要100次分配,分配 后数组的各元素&#20540;为null,然后再初始化100个元素,结果总共需要进行101次分配。这将消耗更多的时间,造成更多的内存碎片。所以,如果类型的职责 主要是存储数据,&#20540;类型比较合适。
一般来说,&#20540;类型(不支持多态)适合存储供 C#应用程序操作的数据,而引用类型(支持多态)应该用于定义应用程序的行为。
通常我们创建的引用类型总是多于&#20540;类型。如果以下问题的回答都为yes,那么我们就应该创建为&#20540;类型:
该类型的主要职责是否用于数据存储?&
该类型的共有借口是否完全由一些数据成员存取属性定义?&
是否确信该类型永远不可能有子类?&
是否确信该类型永远不可能具有多态行为?
5.2 将&#20540;类型尽可能实现为具有常量性和原子性的类型
具有常量性的类型很简单:
如果构造的时候验证了参数的有效性,之后就一直有效;&
省去了许多错误检查,因为禁止更改;&
确保线程安全,因为多个reader访问到同样的内容;&
可以安全地暴露给外界,因为调用者不能更改对象的内部状态。
具有原子性的类型都是单一的实体,我们通常会直接替换一个原子类型的整个内容。
下面是一个典型的可变类型:
public struct Address
private string _
private string _
private int _zipC
public string City
get { return _ }
set { _city = }
public string Province
get { return _ }
ValidateProvince(value);
_province =
public int ZipCode
get { return _zipC }
ValidateZipCode(value);
_zipCode =
下面创建一个实例:
Address address = new Address();
address.City = &Chengdu&;
address.Province = &Sichuan&;
address.ZipCode = 610000;
然后更改这个实例:
address.City = &Nanjing&; //Now Province and ZipCode are invalid
address.ZipCode = 210000; //Now Province is still invalid
address.Province = &Jiangsu&;
可见,内部状态的改变意味着可能违反对象的不变式(invariant),至少是临时的违反。如果上面是一个多线程的程序,那么在 City更改的过程中,另一个线程可能看到不一致的数据视图。如果不是多线程的程序,也有问题:
当ZipCode的&#20540;无效而抛出异常时,对象仅作了一部分改变,因此处于无效的状态,为了修复这个问题,需要在Address中添加相当多的内部校验代码;&
为了实现异常安全,我们需要在所有改变多个字段的客户代码处放上防御性的代码;&
线程安全也要求我们在每一个属性的访问器上添加线程同步检查。
显然,这是一个相当可观的工作量。下面我们把Address实现为常量类型:
public struct Address
private string _
private string _
private int _zipC
public Address (string city, string province, int zipCode)
_province =
_zipCode = zipC
ValidateProvince(province);
ValidateZipCode(zipCode);
public string City
get { return _ }
public string Province
get { return _ }
public int ZipCode
get { return _zipC }
如果要改变Address,不能修改现有的实例,只能创建一个新的实例:
Address address = new Address(&Chengdu&, &Sichuan&, 610000);//create a instance
address = new Address(&Nanjing&, &Jiangsu&, 210000);//modify the instance
address将不存在任何无效的临时状态。那些临时状态只存在于Address的构造函数执行过程中。这样一来,Address是异常安全的,也是线程安全的。
5.3 确保0为&#20540;类型的有效状态
.NET的默认初始化机制会将引用类型设置为二进制意义上的0,即null。而对于&#20540;类型,不论我们是否提供构造函数,都会有一个默认的构造函数,将其设置为0。
一种典型的情况是枚举:
public enum Sex
Female = 2;
然后用做&#20540;类型的成员:
public struct Employee
private Sex _
创建Employee结构体将得到一个无效的Sex字段:
Employee employee = new Employee ();
employee的_sex是无效的,因为其为0。我们应该将0作为一个为初始化的&#20540;明确表示出来:
public Sex
Female = 2;
如果&#20540;类型中包含引用类型,会出现另一种初始化问题:
public struct ErrorLog
private string _
然后创建一个ErrorLog:
ErrorLog errorLog = new ErrorLog ();
errorLog的_message字段将是一个空引用。我们应该通过一个属性来将_message暴露给客户代码,从而使该问题限定在ErrorLog 的内部:
public struct ErrorLog
private string _
public string Message
return (_message ! = null) ? _message : string.E
set { _message = }
5.4 尽量减少装箱和拆箱
装箱指把一个&#20540;类型放入一个未具名类型的引用类型中,比如:
int valueType = 0;
object referenceType =//boxing
拆箱则是从前面的装箱对象中取出&#20540;类型:
object referenceT
int valueType = (int)referenceT//unboxing
装箱和拆箱是比较耗费性能的,还会引入一些诡异的bug,我们应当避免装箱和拆箱。
装箱和拆箱最大的问题是会自动发生。比如:
Console.WriteLine(&A few numbers: {0}, {1}.&, 25, 32);
其中,Console.WriteLine()接收的参数类型是(string,object,object)。因此,实际上会执行以下操作:
int i = 25;
obeject o =//boxing
然后把o传给WriteLine()方法。在WriteLine()方法的内部,为了调用i上的ToString()方法,又会执行:
int i = (int)o;//unboxing
string output = i,ToString();
所以正确的做法应该是:
Console.WriteLine(&A few numbers: {0}, {1}.&, 25.ToString(), 32.ToString());
25.ToString()只是执行一个方法并返回一个引用类型,不存在装箱/拆箱的问题。
另一个典型的例子是ArryList的使用:
public struct Employee
private string _
public Employee(string name)
public string Name
get { return _ }
set { _name = }
public override string ToString()
ArrayList employees = new ArrayList();
employees.Add(new Employee(&Old Name&));//boxing
Employee ceo = (Employee)employees[0];//unboxing
ceo.Name = &New Name&;//employees[0].ToString() is still &Old Name&
上面的代码不仅存在性能的问题,还容易导致错误发生。
在这种情况下,更好的做法是使用泛型集合:
List&Employee& employees = new List&Employee&();
由于List&T&是强类型的集合,employees.Add()方法不进行类型转换,所以不存在装箱/拆箱的问题。
C#中,变量是&#20540;还是引用仅取决于其数据类型。
C#的&#20540;类型包括:结构体(数&#20540;类型,bool型,用户定义的结构体),枚举,可空类型。
C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。
数组的元素,不管是引用类型还是&#20540;类型,都存储在托管堆上。
引用类型在栈中存储一个引用,其实际的存储位置位于托管堆。为了方便,本文简称引用类型部署在托管推上。
&#20540;类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实例)存储;作为局部变量时,存储在栈上。
&#20540;类型在内存管理方面具有更好的效率,并且不支持多态,适合用作存储数据的载体;引用类型支持多态,适合用于定义应用程序的行为。
应该尽可能地将&#20540;类型实现为具有常量性和原子性的类型。
应该尽可能地确保0为&#20540;类型的有效状态。
应该尽可能地减少装箱和拆箱。
Effective C#&
Professional C#&
Programming .NET Components&
C#语言规范&
Type Fundamentals
> 本站内容系网友提交或本网编辑转载,其目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及作品内容、版权和其它问题,请及时与本网联系,我们将在第一时间删除内容!
在C#中有两种类型:值类型和引用类型.值类型的变量直接包含他们的数据,而引用类型的变量存储对它们数据的引用. 值类型包括:简单类型.整型.浮点型.decimal类型.Bool类型和枚举类型 引用类型包括:类类型.对象类型.string类型.接口类型.数组类型.委托类型. 值类型是分配在栈上的数据,引用类型是分配在堆上的数据. C#中支持从值类型到引用类型的转 ...
关于值类型和引用类型已经有很多人写了很多文章,但是很多人也只是停留在字面上的理解,如果采用一种通俗的方法来解释,想必很多人都会理解.我们都知道值类型存储在栈上,引用类型存储在堆上,引用类型都是xxx类,值类型都是xxx结构(structure).下面先放一张图 // 引用类型 因为有class class SomeRef { public Int32
网上偶尔浏览到这一篇文章,还不错就修改了下分享给大家.
工作许久了,可是对C#值类型和C#引用类型却一直无法很好的理解.这两天花了不少时间查找资料,看文章,终于有所收获,在此将自己理解整理出来,方便日 后自己查看,同时希望对跟我有一样困惑的朋友有所帮助.废话不多说,下面开始说说怎么理解值类型和引用类型!
C#值类型数据直接在他 ...
工作许久了,可是对C#中的值类型和引用类型却一直无法很好的理解.这两天花了不少时间查找资料,看文章,终于有所收获,在此将自己理解整理出来,方便日后自己查看,同时希望对跟我有一样困惑的朋友有所帮助.废话不多说,下面开始说说怎么理解值类型和引用类型! 值类型数据直接在他自身分配到的内存中存储数据,而引用类型只是包含指向存储数据位置的指针. 那么有哪些类型是值类型 ...
今日查询.net中string属哪种类型时找到这篇文章,写得很清楚,转过来大家看.
从概念上看,值类型直接存储其值,而引用类型存储对其值的引用.这两种类型存储在内存的不同地方.在C#中,我们必须在设计类型的时候就决定类型实例的行为.这种决定非常重要,用&CLR via C#&作者Jeffrey Richter的话来说,“不理解引用类型和值类型 ...
转自:/mrcooldog/archive//1088769.html这篇文章是我几个月前写的,今天进行了比较大的修订,重新发了出来,希望和大家共同探讨,并在此感谢Anytao的讨论和帮助.从概念上看,值类型直接存储其值,而引用类型存储对其值的引用.这两种类型存储在内存的不同地方.在C#中,我们 ...
我刚用C#一个来月,可能理解得不对,还请大家指教. 读懂文章你需要对C语言的指针有所理解. 需要注意区别:对C\C++来说,任何类型都可以当成C#的&引用类型&,因为有指针. void foo() { int aaa = 0; //值类型,aaa在Stack上分配(SUB ESP,XX) int* paaa = new int[123]; ...
从概念上看,值类型直接存储其值,而引用类型存储对其值的引用.这两种类型存储在内存的不同地方.在C#中,我们必须在设计类型的时候就决定类型实例的行为.这种决定非常重要,用&CLR via C#&作者Jeffrey Richter的话来说,“不理解引用类型和值类型区别的程序员将会给代码引入诡异的bug和性能问题(I believe that a de ...

我要回帖

更多关于 java string引用类型 的文章

 

随机推荐