今天在看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针对ActiveMQ JMS Point To Point压力测试

    准备工作 针对JMS类型的Sampler,需要额外的jar包(这里用的是apache ActiveMQ,将下载的AMQ apache-activemq-5.5.0根目录下的activemq-all-5 ...

  2. 1060 Are They Equal

    题意: 给出两个浮点数(最大不超过10^100),以及存储的有效位数,判断这两个数是否相等.如12300和12358.9若存储的有效位数为3,则均表示为0.123*10^5,因此视为相等. 思路:[字 ...

  3. 【转载】解决SQL Server 阻止了对组件 'Ad Hoc Distributed Queries' 的 STATEMENT'OpenRowset/OpenDatasource' 的访问的方法

    1.开启Ad Hoc Distributed Queries组件,在sql查询编辑器中执行如下语句: exec sp_configure 'show advanced options',1 recon ...

  4. springboot成神之——springboot入门使用

    springboot创建webservice访问mysql(使用maven) 安装 起步 spring常用命令 spring常见注释 springboot入门级使用 配置你的pom.xml文件 配置文 ...

  5. linux之sort用法

    sort命令是帮我们依据不同的数据类型进行排序,其语法及常用参数格式: sort [-bcfMnrtk][源文件][-o 输出文件] 补充说明:sort可针对文本文件的内容,以行为单位来排序. 参 数 ...

  6. 【转】VS 安全开发生命周期(SDL)检查

    前面在学习使用google的protobuf时在VS2012中一直无法编译编译通过,经过查找一些资料原来发现,并不是protobuf的问题,而是自己在使用VS2012时,没有完全了解VS2012的强大 ...

  7. MyEclipse从数据库反向生成实体类通过Hibernate的方式----mysql数据库实例

    1.我们通过DB与数据库建立连接 2.建立web工程,构建Hibernate框架 3.通过table生成实体类

  8. 201671010127 2016-2017-8 回谈static修饰符

    上周学了泛型程序程序设计技术,再一次接触到了静态方法,那么今天就来谈一下static修饰符. static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块, ...

  9. Linux修复MBR扇区故障

    给虚拟机增加一块硬盘,用于备份mbr的信息 fdisk -l 查看硬盘系统是否认识 fdisk /dev/sdb 进行分区 fdisk -l 查看分区是否出来 mkfs -t ext4 /dev/sb ...

  10. Python 安装urllib3

    一.系统环境 操作系统:Win10 64位 Python版本:Python 3.7.0 二.报错信息 No module named 'urllib3' 三.安装参考 1.参照网上的安装方法通过pip ...