关于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函数的比较步骤是先比较两个对象的引用是否相等,不相等的话再对值进行比较,比较值时是按位比较的。

4、String与string的区别

string是String的别名而已,string是c#中的类,String是Framework的类,C# string 映射为 Framework的 String。如果用string,编译器会把它编译成String,所以如果直接用String就可以让编译器少做一点点工作。

如果使用C#,建议使用string,比较符合规范 。 string始终代表 System.String(1.x) 或 ::System.String(2.0) ,String只有在前面有using System;的时候并且当前命名空间中没有名为String的类型(class、struct、delegate、enum)的时候才代表System.String。

string是关键字,String不是,也就是说string不能作为类、结构、枚举、字段、变量、方法、属性的名称,而String可以。

5、String传值还是传引用

C#的String声明是class String,当然是传引用。

不过,之所以有这个疑惑,多数是因为这个情况:
string a = "aaa";
string b = a;
b = "bbb";
或者是这么几行代码:
public void Swap(string s1, string s2)
{
    string temp=s1;
    s1=s2;
    s2=temp;
}
这时候结果一打印,结果发现a的值还没有变,Swap也没有成功,这时候就会有幻觉:是不是没有传引用啊?
呵呵,string不会这么粗暴的打乱“声明为class就是传引用”这种规则的。
分析一下:
string a = "aaa"; //==> a----->new String("aaa")
string b = a;        //==> b----->a, 传引用
b = "bbb";          //==> b----->new String("bbb"), 传引用,b指向了一个新的字符串,a并没有变。
Swap函数也是这样,比如说传了a, b进去(a="aaa", b="bbb"),
    //s1----->a, s2----->b
    string temp=s1;//temp----->s1----->a
    s1=s2;               //s1----->s2----->b;
    s2=temp;          //s2----->temp----->a
结果是,s1和s2确实是Swap了,但是这种结果并不会影响到a和b

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

  1. Java 干货之深入理解String

    可以证明,字符串操作是计算机程序设计中最常见的行为,尤其是在Java大展拳脚的Web系统中更是如此. ---<Thinking in Java> 提到Java中的String,总是有说不完 ...

  2. 深入理解String、StringBuffer、StringBuilder(转)

    文章系转载,非原创,原地址: http://www.cnblogs.com/dolphin0520/p/3778589.html 相信String这个类是Java中使用得最频繁的类之一,并且又是各大公 ...

  3. Java基础系列2:深入理解String类

    Java基础系列2:深入理解String类 String是Java中最为常用的数据类型之一,也是面试中比较常被问到的基础知识点,本篇就聊聊Java中的String.主要包括如下的五个内容: Strin ...

  4. Java提高篇——理解String 及 String.intern() 在实际中的应用

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

  5. 跟着刚哥梳理java知识点——深入理解String类(九)

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

  6. Java基础3:深入理解String及包装类

    更多内容请关注微信公众号[Java技术江湖] 这是一位阿里 Java 工程师的技术小站,作者黄小斜,专注 Java 相关技术:SSM.SpringBoot.MySQL.分布式.中间件.集群.Linux ...

  7. 深入理解String类详解

    1.Stringstr = "eee" 和String str = new String("eee")的区别 先看一小段代码, 1 public static ...

  8. c#基础系列2---深入理解 String

    "大菜":源于自己刚踏入猿途混沌时起,自我感觉不是一般的菜,因而得名"大菜",于自身共勉. 扩展阅读:深入理解值类型和引用类型 基本概念 string(严格来说 ...

  9. 深入理解String类

    1.String str = "eee" 和String str = new String("eee")的区别 先看一小段代码, public static v ...

  10. 几张图轻松理解String.intern()

    https://blog.csdn.net/soonfly/article/details/70147205 在翻<深入理解Java虚拟机>的书时,又看到了2-7的 String.inte ...

随机推荐

  1. SqlServer给一个表增加多个字段语法

    添加字段语法 alter table table_name add column_name +字段类型+ 约束条件 给一个表增加多个字段: use NatureData go alter table ...

  2. 《Servlet和jsp学习指南》 笔记2

    chapter 13 请求和响应的装饰 初步了解Decorator模式: 在不修改一个对象的类的源码的情况下,装饰这个对象的行为. chapter 14 异步处理 异步Servlet和Filter,只 ...

  3. nginx理解与配置

    准备: http服务器:①tomcat②apache③nginx(c语言开发) 文件系统:①mgfs ②mgbd:存储小文件 ③fastDFS:存储大文件.小文件,分布式文件系统 nginx是一种ht ...

  4. c++实验2 顺序存储线性表

    线性表顺序存储 实现了动态数组的增删改查  前驱后继  A=AUB 动态数组右移 (1)顺序表存储结构的定义(类的声明): class SeqList { protected: DataType *l ...

  5. Markdown使用样例

    # 欢迎使用 Cmd - 在线 Markdown 编辑阅读器 ------ 我们理解您需要更便捷更高效的工具记录思想,整理笔记.知识,并将其中承载的价值传播给他人,**Cmd Markdown** 是 ...

  6. webservice怎么给对方提供报文,即wsdl文件

    1.webservice发布后在网页打开服务,点击服务说明 2.打开这样一个页面,ctrl+s保存网页,后缀改为wsdl,搞定

  7. webstorm激活服务器地址

    2017.1.4版本可用 http://idea.imsxm.com/

  8. 《Andrew Ng深度学习》笔记3

    浅层神经网络 初步了解了神经网络是如何构成的,输入+隐藏层+输出层.一般从输入层计算为层0,在真正计算神经网络的层数时不算输入层.隐藏层实际就是一些算法封装成的黑盒子.在对神经网络训练的时候,就是对神 ...

  9. [WebShow系列] 固定展示界面的现场调用

    正在制作......,敬请期待. 固定展示界面的现场调用 现场管理员通过现场控制台可以控制主展示界面,实现 主题 所选选手展示 所选选手打分展示 排行展示 详情排行展示 柱状图展示 界面的展示切换外, ...

  10. 字符串变dict

    1.eval 2.json # NameError: name # 'null' is not defined # i_dict=eval(i) 这种方式,如果dict字符串中有null ,将不能变成 ...