今天在看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. TCP之四:TCP 滑动窗口协议 详解

    滑动窗口机制 滑动窗口协议的基本原理就是在任意时刻,发送方都维持了一个连续的允许发送的帧的序号,称为发送窗口:同时,接收方也维持了一个连续的允许接收的帧的序号,称为接收窗口.发送窗口和接收窗口的序号的 ...

  2. 权益保护-知识产权:知识产权(IP)百科

    ylbtech-权益保护-知识产权:知识产权(IP)百科 知识产权,也称其为“知识所属权”,指“权利人对其智力劳动所创作的成果和经营活动中的标记.信誉所依法享有的专有权利”,一般只在有限时间内有效.各 ...

  3. python学习(十五) 屏幕抓取

    15.1 屏幕抓取 15.1.1 Tidy和XHTML解析 Tidy:用来修复不规范且随意的HTML文档的工具. 为什么用XHTML: 和旧版本的HTML之间最主要的区别:HTML可能只用一个开始标签 ...

  4. 单片机keil C中的data、bdata、idata、xdata、hdata、pdata、code解释

    从数据存储类型来说,8051系列有片内.片外程序存储器,片内.片外数据存储器,片内程序存储器还分直接寻址区和间接寻址类型,分别对应code.data.xdata.idata以及根据51系列特点而设定的 ...

  5. MVC中Ajax post 和Ajax Get——提交对象Model

    HTTP 请求:GET vs. POST两种在客户端和服务器端进行请求-响应的常用方法是:GET 和 POST.GET - 从指定的资源请求数据POST - 向指定的资源提交要处理的数据GET 基本上 ...

  6. verilog 之语法学习

    1.使用非基数表示的十进制视为有符号数.使用基数表示的十进制被视为无符号数. 2.线网中的值被解释为无符号数,整型寄存器中的值被解释为有符号的二进制补码数,. 3.如果选择表达式的值为 x.z,或越界 ...

  7. Java微信公众平台开发(二)--微信服务器post消息体的接收

    转自: http://www.cuiyongzhi.com/post/39.html 在上一篇的文章中我们详细讲述了如何将我们的应用服务器和微信腾讯服务器之间的对接操作,最后接入成功,不知道你有没有发 ...

  8. DataGridView根据条件给单元格绑定图片

    代码区: private void Form1_Load(object sender, EventArgs e) { myClass.mySqliteAPI conn = new myClass.my ...

  9. instanceof php

    instdnceof php5  的一个新成员 功能: 使用这个关键字可以确定一个对象是否是类的实例,是否是累的子类 ,还是实现了某个特定的接口. <?php class A{} class B ...

  10. SpringBoot16 MockMvc的使用、JsonPath的使用、请求参数问题、JsonView、分页查询参数、JsonProperty

    1 MockMvc的使用 利用MockMvc可以快速实现MVC测试 坑01:利用MockMvc进行测试时应用上下文路径是不包含在请求路径中的 1.1 创建一个SpringBoot项目 项目脚手架 1. ...