关于C#中的类型

在C#中类型分为值类型和引用类型,引用类型和值类型都继承自System.Object类,几乎所有的引用类型都直接从System.Object继承,而值类型具体一点则继承System.Object的子类,即继承System.ValueType。而String类型却有点特别,虽然它属于引用类型,但是他的一些特性却有点类似值类型。

关于C# String

1、不变性

我们先来看看一个例子:

static void Main(string[] args)
{
string str1 = "string";
string str2 = str1;
Console.WriteLine(object.ReferenceEquals(str1, str2));
str2 += "change";
Console.WriteLine(object.ReferenceEquals(str1, str2));
Console.ReadKey();
}

输出结果是True、False。为什么呢?我们来看看IL。

.entrypoint
// 代码大小 48 (0x30)
.maxstack 2
.locals init ([0] string str1,
[1] string str2)
IL_0000: nop
IL_0001: ldstr "string"
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: stloc.1
IL_0009: ldloc.0
IL_000a: ldloc.1
IL_000b: ceq
IL_000d: call void [mscorlib]System.Console::WriteLine(bool)
IL_0012: nop
IL_0013: ldloc.1
IL_0014: ldstr "change"
IL_0019: call string [mscorlib]System.String::Concat(string,string)
IL_001e: stloc.1
IL_001f: ldloc.0
IL_0020: ldloc.1
IL_0021: ceq
IL_0023: call void [mscorlib]System.Console::WriteLine(bool)
IL_0028: nop
IL_0029: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
IL_002e: pop
IL_002f: ret

+=在内部调用了Concat函数,将str2和"change"连接起来直接生成了一个新的字符串,和原来的字符串是不同的对象。Trim、Remove函数都是会直接生成一个新的对象,字符串一经定义,就不能改变。

其实字符串具有原子性(也就是不变性),任何改变字符串的值的行为都不会成功,只会创建一个新的字符串对象。在实际编程中,我们会大量的使用字符串,这样就会导致不停地创建新的字符串对象和分配内存,可能导致垃圾回收器GC不停地进行垃圾回收,大大降低性能,并且伴随着内存溢出的危险。所以.Net对字符串进行了的特殊的处理,这就是字符串驻留池。

在字符串驻留池,保存着字符串字面值和指向的引用。每次有新的字符串创建,都会在驻留池中查找是否存在字面值相同的字符串,如果存在就将其指向已经存在的字符串的引用,不存在就直接新建一个字符串,然后指向一个新的地址。

2、作为函数参数的处理

在函数的参数传递中,值类型直接拷贝变量保存的值,传递的是一个值得副本,而引用类型传递的是地址的一个副本,所以在函数中改变引用参数中属性的值会直接改变函数外真实类型对象的值。

static void Main(string[] args)
{
People people = new People() { Name = "Jack" };
Console.WriteLine(people.Name);
Change(people);
Console.WriteLine(people.Name);
Console.ReadKey();
} static void Change(People p)
{
p.Name = "Eason";
} class People
{
public string Name { get; set; }
}

程序先输出Jack,后输出Eason,可以说明引用类型传递的是引用地址,函数改变的参数对象和外部传递进来的对象是一个对象。

那么我们来看看String作为参数的情况:

static void Main(string[] args)
{
string str = "string";
Console.WriteLine(str);
Change(str);
Console.WriteLine(str);
Console.ReadKey();
} static void Change(string str)
{
str = "change";
Console.WriteLine(str);
}

结果输出string、change、string。调用Change函数后str的值还是"string",由于字符串类型的不变性,在Change函数中对str进行赋值会重新创建一个新的字符串对象,然后为这个新的对象附上引用。所以虽然字符串类型是引用类型,但是在参数传递时它其实相当于值类型。

3、相等比较处理

先看一个例子:

string str1 = "string";
string str2 = "string";
string str3 = "stringstring";
string str4 = "string" + "string";
string str5 = str1 + "string";
Console.WriteLine(ReferenceEquals(str1, str2));
Console.WriteLine(str1 == str2);
Console.WriteLine(ReferenceEquals(str3, str4));
Console.WriteLine(str3 == str4);
Console.WriteLine(ReferenceEquals(str3, str5));
Console.WriteLine(str3 == str5);
Console.ReadKey();

不出意外结果都应该为True,True,True,True,True,True,但是结果却是True,True,True,True,False,True,str3和str5不是一个对象,他们不是指向同一个地址,为什么呢?经过查看IL代码发现,str5在IL代码中调用了Concat函数将str1和"string"进行了拼接,那这个Concat函数到底做了什么。

public static string Concat(string str0, string str1)
{
if (IsNullOrEmpty(str0))
{
if (IsNullOrEmpty(str1))
{
return Empty;
}
return str1;
}
if (IsNullOrEmpty(str1))
{
return str0;
}
int length = str0.Length;
string dest = FastAllocateString(length + str1.Length);
FillStringChecked(dest, 0, str0);
FillStringChecked(dest, length, str1);
return dest;
}

FastAllocateString函数负责分配长度为str0.Length+str1.Length的空字符串dest,FillStringChecked分别将str0和str1复制到dest中,最后生成由str0和str1连接成的字符串,这样不会再去字符串驻留池中查找是否存在和dest相同的字符串,而是直接生成一个新的对象。所以字符串变量和字符串常量进行拼接后会直接生成一个新的对象,绕过驻留池检查。

而字符串常量拼接不会产生新的字符串,除非驻留池中没有与之拼接后字面值相等的字符串。我们来看看IL代码:

  IL_0001:  ldstr      "string"
IL_0006: stloc.0
IL_0007: ldstr "string"
IL_000c: stloc.1
IL_000d: ldstr "stringstring"
IL_0012: stloc.2
IL_0013: ldstr "stringstring"
IL_0018: stloc.3
IL_0019: ldloc.0
IL_001a: ldstr "string"
IL_001f: call string [mscorlib]System.String::Concat(string,string)
IL_0024: stloc.s str5
IL_0026: ldloc.0
IL_0027: ldloc.1

str3和str4的字面值是相等的,都是"stringstring",str3先于str4被初始化,当str4被初始化的时候,由于其字面值和str3相等,所以CLR会将str3指向的地址赋给str4,所以str3和str4引用是相等的。

至于""操作符的得到的结果都是True是因为""操作符会调用String.Equal方法,IL代码如下:

  IL_0032:  call       bool [mscorlib]System.String::op_Equality(string,string)

op_Equality最终会调用String.Equal函数,Equal函数的比较步骤是先比较两个对象的引用是否相等,不相等的话再对值进行比较,比较值时是按位比较的。

深入理解C#中的String的更多相关文章

  1. 深入理解Java中的String

    一.String类 想要了解一个类,最好的办法就是看这个类的实现源代码,来看一下String类的源码: public final class String implements java.io.Ser ...

  2. 【转】深入理解Java中的String

    原文链接:http://www.cnblogs.com/xiaoxi/p/6036701.html 一.String类 想要了解一个类,最好的办法就是看这个类的实现源代码,来看一下String类的源码 ...

  3. 深刻理解Java中的String、StringBuffer和StringBuilder的差别

    声明:本博客为原创博客,未经同意.不得转载!小伙伴们假设是在别的地方看到的话,建议还是来csdn上看吧(链接为http://blog.csdn.net/bettarwang/article/detai ...

  4. JDK学习---深入理解java中的String

    本文参考资料: 1.<深入理解jvm虚拟机> 2.<大话数据结构>.<大话设计模式> 3.http://www.cnblogs.com/ITtangtang/p/3 ...

  5. 全面理解Java中的String数据类型

    1. 首先String不属于8种基本数据类型,String是一个对象. 因为对象的默认值是null,所以String的默认值也是null:但它又是一种特殊的对象,有其它对象没有的一些特性. 2. ne ...

  6. 深刻理解Java中final的作用(一):从final的作用剖析String被设计成不可变类的深层原因

    声明:本博客为原创博客,未经同意,不得转载!小伙伴们假设是在别的地方看到的话,建议还是来csdn上看吧(原文链接为http://blog.csdn.net/bettarwang/article/det ...

  7. JDK学习---深入理解java中的HashMap、HashSet底层实现

    本文参考资料: 1.<大话数据结构> 2.http://www.cnblogs.com/dassmeta/p/5338955.html 3.http://www.cnblogs.com/d ...

  8. JDK学习---深入理解java中的LinkedList

    本文参考资料: 1.<大话数据结构> 2.http://blog.csdn.net/jzhf2012/article/details/8540543 3.http://blog.csdn. ...

  9. Java内存管理-探索Java中字符串String(十二)

    做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 一.初识String类 首先JDK API的介绍: public final class String extends O ...

随机推荐

  1. git教程(简单的带你学好git)

    刚开始使用的时候没有找到好的资源学习,下面这个资源不错,大家可以参考学习. http://www.liaoxuefeng.com/wiki/0013739516305929606dd183612485 ...

  2. 【react学习】关于react框架使用的一些细节要点的思考

    ( _(:3 」∠)_给园友们提个建议,无论是API文档还是书籍,一定要多看几遍!特别是隔一段时间后,会有意想不到的收获的)   这篇文章主要是写关于学习react中的一些自己的思考:   1.set ...

  3. CGLIB和JDK代理

    需要的架包:在spring中提供对CGLIB的支持 一.JDK的动态代理 1.接口IUserDao package cn.itcast.spring3.jdk.proxy; public interf ...

  4. MapReduce中一次reduce方法的调用中key的值不断变化分析及源码解析

    摘要:mapreduce中执行reduce(KEYIN key, Iterable<VALUEIN> values, Context context),调用一次reduce方法,迭代val ...

  5. android奋战的一周

    移动开发项目终于开始了,我也开始紧张起来了,就靠着培训时候学的一知半解的知识,作为一个主力不得不逼着自己不停的做实验,不停的学习. 有辛苦就有回报,我也对android开发中整体的框架有了些许的了解. ...

  6. [ext4]空间管理 - 与分配相关的关键数据结构

     在块分配机制中,涉及到几个主要的数据结构. 通过ext4_allocation_request描述块请求,然后基于块查找结果即上层需求来决定是否执行块分配操作. 在分配过程中,为了更好执行分配, ...

  7. HTML5 拖放(Drag 和 Drop)功能开发——基础实战

    随着HTML5的普及度越来越高,现在写代码也遇到一些了,经过同事的点播开展了一次Dojo活动用以技术交流,我也乘此机会将HTML5的拖放功能整理了一下. 简介 拖拽(Drag/Drop)是个非常普遍的 ...

  8. 微坑---微信小程序ios上时间字符串转换为时间戳时,在开发工具上和安卓手机上运行成功

    给定一个时间字符串  var time="2017-02-27 16:42:53" js有三种转换为时间戳的方法:1.var timestamp = Date.parse(time ...

  9. 全易通人事考勤工资验厂管理系统软件创建连接SQL2000数据库的操作方法和说明

    全易通人事考勤工资验厂管理系统软件创建连接SQL2000数据库的操作方法和说明.全易通人事考勤工资验厂管理系统软件,有2种数据库,一个是ACCESS,另一个是SQL.不过由于ACCESS数据库比较小, ...

  10. Unity 多屏(分屏)显示,Muti_Display

    Unity 多屏(分屏)显示,Muti_Display  最近项目有个需求,主要用于在展厅的展示游戏. 比如,在一个很大的展厅,很大的显示屏挂在墙上,我们不可能通过操作墙上那块显示器上的按钮来控制游戏 ...