关于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. 【大数据之数据仓库】GreenPlum PK DeepGreen(TPCH)

    1.背景 一张UML类图可以简单的说明GreenPlum和DeepGreen之间的关系: GreenPlum: 主页:http://greenplum.org/ 源码:开源,https://githu ...

  2. 「HAOI2016」放棋子

    题目链接 戳这 前置知识 错位排序 Solution 我们可以观察发现,每一行的障碍位置对答案并没有影响. 于是我们可以将此时的矩阵化成如下形式: \[ 1\ \ 0\ \ 0\ \ 0\\ 0\ \ ...

  3. Swoole http server + yaf, swoole socket server + protobuf 等小结

    拥抱swoole, 拥抱更好的php Swoole 是什么? Yaf 是什么? 接触swoole已经4年多了,一直没有好好静下心来学习.一直在做web端的应用,对网络协议和常驻内存型服务器一窍不通.一 ...

  4. django中如何建立抽象型数据库作为父模块可继承其功能

    先建立抽象数据库 from django.db import models class BaseModel(models.Model): """为模型类补充字段" ...

  5. 题解 P1614 【爱与愁的心痛】

    题目链接 前缀和. #重点在一个小小的常数优化 但是数据大了以后比楼下们跑的会快!!! 楼下用前缀和的题解都是跑了两遍循环. 而实际上一遍循环就可以呀. 就是加一段这个 if(i>=m) if( ...

  6. vmware vSphere虚拟网络之标准交换机(二)

    一.标准交换机的特点: 1)只能用于物理主机 2)其他主机不能共享同一个虚拟交换机 3)不具备任何灵活性 4)每台ESXi主机都要配置一遍 二.网络图: 三.创建标准交换机: 登录web vCente ...

  7. 蓝牙4.0BLE抓包(三) – 扫描请求和扫描响应

    版权声明:本文为博主原创文章,转载请注明作者和出处.    作者:强光手电[艾克姆科技-无线事业部] 1. 扫描请求和扫描响应 广播包含扫描请求SCAN_REQ和扫描响应SCAN_RSP. 扫描请求: ...

  8. linux 下系统时间设置C语言实现

    #include <stdio.h> #include <stdlib.h> #include <time.h> #include <sys/time.h&g ...

  9. redux超易学三篇之一(单独说redux)

    redux其实非常简单.当复杂的步骤被拆分,其实每一步都是很容易的. Github: 完整代码链接 本文在 create-react-app 中的 index.js 随便引入了一下. (其实不必如此. ...

  10. HDU_1028 Ignatius and the Princess III 【母函数的应用之整数拆分】

    题目: "Well, it seems the first problem is too easy. I will let you know how foolish you are late ...