今天在看C#编程指南时(类型参数的约束http://msdn.microsoft.com/zh-cn/library/d5x73970.aspx)看到一段描述:

在应用 where T : class 约束时,避免对类型参数使用 == 和 != 运算符,因为这些运算符仅测试引用同一性而不测试值相等性。即使在用作参数的类型中重载这些运算符也是如此。下面的代码说明了这一点;即使 String 类重载 == 运算符,输出也为 false。

并给出的代码

public static void OpTest<T>(T s, T t) where T : class
{
System.Console.WriteLine(s == t);
}
static void Main()
{
string s1 = "target";
System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
string s2 = sb.ToString();
OpTest<string>(s1, s2);
}

返回的结果为False即s1!=s2
到这里都十分容易理解,因为在C#中==运算符对于值类型,如果对象的值相等,则相等运算符 (==) 返回 true,否则返回 false。对于引用类型,如果两个对象引用同一个对象,则 == 返回 true。

所以如果我将代码改为:

static void Main()
{
  string s1 = "target";
  System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
  string s2 = sb.ToString();
  s2 = s1;
  OpTest<string>(s1, s2);
}

返回的结果为TRUE即s1==s2,因为s1和s2引用同一个对象。

接下来我又做了一个实验(重点来了):

static void Main()
{  
string s1 = "target";  
string s2 = "target";  
OpTest<string>(s1, s2);
}

返回的结果依然是TRUE即s1==s2,这个结果完全出乎我的预料!思考中......

==这里其实有一个很大的陷阱!上面的理解有一处很大的破绽!因为笔者忘记了很重要的一条!

在C#参考中有以下描述(== 运算符(C# 参考)http://127.0.0.1:47873/help/1-30636/ms.help?product=VS&productVersion=100&method=f1&query=%3D%3D_CSharpKeyword%00%3D%3D&locale=zh-CN&category=DevLang%3acsharp%00TargetFrameworkMoniker%3a.NETFramework,Version%3Dv4.0

对于预定义的值类型,如果操作数的值相等,则相等运算符 ( ==) 返回 true,否则返回 false。 对于 string 以外的引用类型,如果两个操作数引用同一个对象,则 == 返回 true。 对于 string 类型, == 比较字符串的值。

也就是说string类型其实有对==运算符进行重载(实时上作者在刚开始阅读C#编程指南时明显理解错误),实时如此:

string s1 = "target";
System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
string s2 = sb.ToString(); if (s1 == s2)
{
  System.Console.WriteLine("==");
}
else
{
  System.Console.WriteLine("!=");
}

结果显示s1 == s2 返回True,而在本文开头提出的C#编程指南时(类型参数的约束)例子中讲的就是虽然string有对==运算符进行重载,但是在范式编程中会被无视,所以要慎用(好吧,笔者的基本功堪忧啊!),那么为啥

string s1 = "target";  
string s2 = "target";  
OpTest<string>(s1, s2);

会得到s1==s2的结果?

让我们换个思考方式:

1.在OpTest<string>(s1, s2);中==判断的是两个操作数是否引用的同一个对象,如果是则返回True,反之False(包含string类型)

2.在上述例子中s1==s2,则证明s1和s2引用的是同一个对象

于是笔者又尝试了

string[] str = new string[];
str[] = "";
str[] = "";
str[] = ""; OpTest<string>(str[], str[]);
string str0 = @"D:\mysher";
string str1 = "D:\\mysher";
OpTest<string>(str0, str1);

等到的结果都是两个string变量引用了同一个对象
所以笔者得到结论当有多个字符串变量包含了同样的字符串实际值时,CLR让它们向同一个字符串对象。

那么既然是这样,Microsoft在C#编程中给出的例子

string s1 = "target";
System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
string s2 = sb.ToString();
OpTest<string>(s1, s2);

s1和s2也应该满足条件,指向同一个对象啊,可事实却相反。

后来笔者在园子里找到了答案,感谢cyoooo7给出了十分详细的解释:

CLR默默地维护了一个叫做驻留池(Intern Pool)的表。这个表记录了所有在代码中使用字面量声明的字符串实例的引用。这说明使用字面量声明的字符串会进入驻留池,而其他方式声明的字符串并不会进入,也就不会自动享受到CLR防止字符串冗余的机制的好处了。但是在不使用字面量声明时,如CLR在为ToString()方法的返回值分配内存时,并不会到驻留池中去检查是否字符串已经存在了,所以会重新分配内存。

为了让编程者能够强制CLR检查驻留池,以避免冗余的字符串副本,String类的设计者提供了一个名为Intern的类方法,如下

string s1 = "target";
System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
string s2 = String.Intern(sb.ToString());
OpTest<string>(s1, s2);

这样s1和s2又指向同一个对象了。
如果有兴趣的朋友可以去看看cyoooo7《原来是这样:C#中字符串的内存分配与驻留池》一文。

C# 由范式编程==运算符引发对string内存分配的思考的更多相关文章

  1. String内存分配

    Java 把内存划分成两种:一种是栈内存,另一种是堆内存.在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的 栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存 ...

  2. JavaScript---网络编程(3)-Object、String、Array对象和prototype属性

    本节学习JavaScript的对象和方法(函数)~ Object 对象 提供所有 JScript 对象通用的功能. obj = new Object([value]) 参数 obj 必选项.要赋值为 ...

  3. 用R实现范式编程

    面向函数范式编程(Functional programming) 模拟简单的随机过程 模拟一个简单的随机过程:从N~(0,1)标准正态分布中产生100个随机值,反复5次得到一个list,再以每个lis ...

  4. IT第九天 - 包、访问修饰符、变量的内存分配、String类中常用方法

    IT第九天 上午 包 1.包的命名规则:域名.项目名称.模块名 2.如:Wfei.com.windows.login 访问限制符 1.四种访问限制符分别对应为: (1)default:默认的,默认为p ...

  5. foreach 引发的值类型与引用类型思考

    用都知道的一句话概括:“引用类型在堆上,栈上只保存引用:值类型即可存放于栈上也可存放于堆上,值类型变量直接存储值本身”. class Program { static void Main(string ...

  6. 解析Java的JNI编程中的对象引用与内存泄漏问题

    JNI,Java Native Interface,是 native code 的编程接口.JNI 使 Java 代码程序可以与 native code 交互——在 Java 程序中调用 native ...

  7. 【VS开发】【编程开发】【C/C++开发】结构体中的数组与指针的内存分配情况说明

    [VS开发][编程开发][C/C++开发]结构体中的数组与指针的内存分配情况说明 标签:[VS开发] [编程开发] 主要是疑惑在结构体定义的数组的内存空间与指针动态分配的内存空间,在地址上连续性.以及 ...

  8. java内存分配和String类型的深度解析

    [尊重原创文章出自:http://my.oschina.net/xiaohui249/blog/170013] 摘要 从整体上介绍java内存的概念.构成以及分配机制,在此基础上深度解析java中的S ...

  9. Sql Server之旅——终点站 nolock引发的三级事件的一些思考

    曾今有件事情让我记忆犹新,那年刚来携程不久,马上就被安排写一个接口,供企鹅公司调用他们员工的差旅信息,然后我就三下五除二的给写好 了,上线之后,大概过了一个月...DBA那边报告数据库出现大量锁超时, ...

随机推荐

  1. 【转】JMeter基础之——录制脚本

    Jmeter 是一个非常流行的性能测试工具,虽然与LoadRunner相比有很多不足,比如:它结果分析能力没有LoadRunner详细:很它的优点也有很多: ● 开源,他是一款开源的免费软件,使用它你 ...

  2. 校赛热身 Problem B. Matrix Fast Power

    找循环节,肯定在40项以内,不会证明. #include <iostream> #include <cstring> #include <string> #incl ...

  3. TCP与UDP比较 以及并发编程基础知识

    一.tcp比udp真正可靠地原因 1.为什么tcp比udp传输可靠地原因: 我们知道在传输数据的时候,数据是先存在操作系统的缓存中,然后发送给客户端,在客户端也是要经过客户端的操作系统的,因为这个过程 ...

  4. Py修行路 python基础(二)变量 字符 列表

    变量 容器 变量名 标记 数据的作用 字符编码 二进制位 = bit1个二进制位是计算机里的最小表示单元 1个字节是计算机里最小的存储单位 8bits = 1Byte =1字节1024Bytes = ...

  5. PHP 乘法口诀表

    echo "乘法口诀表<br>"; for($i=1;$i<10;$i++) { for ($j = 1; $j <= $i; $j++) printf(& ...

  6. rtmp发送H264及aac的音视频 (转)

    RTMP推送的音视频流的封装形式和FLV格式相似,由此可知,向FMS推送H264和AAC直播流,需要首先发送"AVC sequence header"和"AAC sequ ...

  7. Closest Common Ancestors

    Write a program that takes as input a rooted tree and a list of pairs of vertices. For each pair (u, ...

  8. Centos 6.5 python 2.6.6 升级到 2.7

    1.查看python的版本 [root@localhost ~]# python -V Python 2.6.6 2.安装python 2.7.3 [root@localhost ~]# yum in ...

  9. C. Ray Tracing——披着搜索外衣的扩展欧几里得

    [题目大意] 给你一个n*m的矩形,光线从(0,0)出发,沿右上方向以每秒根号2米的速度运动,碰到矩形边界就会反弹(符合物理规律的反弹),询问k个点,这些点都在矩形内部且不在矩形边界上,求光经过这些点 ...

  10. 分布式文件系统MFS(moosefs)实现存储共享

    分布式文件系统MFS(moosefs)实现存储共享(第二版) 作者:田逸(sery@163.com) 由于用户数量的不断攀升,我对访问量大的应用实现了可扩展.高可靠的集群部署(即lvs+keepali ...