C# 由范式编程==运算符引发对string内存分配的思考
今天在看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内存分配的思考的更多相关文章
- String内存分配
Java 把内存划分成两种:一种是栈内存,另一种是堆内存.在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的 栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存 ...
- JavaScript---网络编程(3)-Object、String、Array对象和prototype属性
本节学习JavaScript的对象和方法(函数)~ Object 对象 提供所有 JScript 对象通用的功能. obj = new Object([value]) 参数 obj 必选项.要赋值为 ...
- 用R实现范式编程
面向函数范式编程(Functional programming) 模拟简单的随机过程 模拟一个简单的随机过程:从N~(0,1)标准正态分布中产生100个随机值,反复5次得到一个list,再以每个lis ...
- IT第九天 - 包、访问修饰符、变量的内存分配、String类中常用方法
IT第九天 上午 包 1.包的命名规则:域名.项目名称.模块名 2.如:Wfei.com.windows.login 访问限制符 1.四种访问限制符分别对应为: (1)default:默认的,默认为p ...
- foreach 引发的值类型与引用类型思考
用都知道的一句话概括:“引用类型在堆上,栈上只保存引用:值类型即可存放于栈上也可存放于堆上,值类型变量直接存储值本身”. class Program { static void Main(string ...
- 解析Java的JNI编程中的对象引用与内存泄漏问题
JNI,Java Native Interface,是 native code 的编程接口.JNI 使 Java 代码程序可以与 native code 交互——在 Java 程序中调用 native ...
- 【VS开发】【编程开发】【C/C++开发】结构体中的数组与指针的内存分配情况说明
[VS开发][编程开发][C/C++开发]结构体中的数组与指针的内存分配情况说明 标签:[VS开发] [编程开发] 主要是疑惑在结构体定义的数组的内存空间与指针动态分配的内存空间,在地址上连续性.以及 ...
- java内存分配和String类型的深度解析
[尊重原创文章出自:http://my.oschina.net/xiaohui249/blog/170013] 摘要 从整体上介绍java内存的概念.构成以及分配机制,在此基础上深度解析java中的S ...
- Sql Server之旅——终点站 nolock引发的三级事件的一些思考
曾今有件事情让我记忆犹新,那年刚来携程不久,马上就被安排写一个接口,供企鹅公司调用他们员工的差旅信息,然后我就三下五除二的给写好 了,上线之后,大概过了一个月...DBA那边报告数据库出现大量锁超时, ...
随机推荐
- Java中计算对象的大小
一.计算对象大小的方法 Java中如何计算对象的大小呢,找到了4种方法: 1.java.lang.instrument.Instrumentation的getObjectSize方法: 2.BTrac ...
- C#简单操作XML
类文件: class OperatorXML { /// <summary> /// 确定资源文件路径,Resource为自己创建的目录 /// </summary> priv ...
- maven学习1
1.Maven的约定 src/main/java: 存放项目的java文件. src/main/resources: 存放项目的资源文件,如spring,hibernate的配置文件. src/te ...
- C++面试题(三)
1 什么是函数对象?有什么作用? 函数对象却具有许多函数指针不具有的有点,函数对象使程序设计更加灵活,而且能够实现函数的内联(inline)调用,使整个程序实现性能加速. 函数对象:这里已经说明了这是 ...
- Proxmark3笔记(一)
Kali下使用Proxmark3 apt-get update apt-get install build-essential libreadline5 libreadline-dev libusb- ...
- transfrom-runtime文档
transfrom-runtime文档 babel只会默认对句法进行转换,而那些方法,api不会转换,要转就要使用polyfill和transform,这里介绍transform,关于polyfill ...
- php学习之if
<html> <head> <title>xxx</title> <style> #tian{ color:blue; float:left ...
- maven项目依赖包问题
问题 maven传递依赖 解决方案 前段时间,开发中遇到一个关于maven依赖包的问题:由于业务需要,支付网关对账代码中的slf4j-api包需要更新,原包为1.5.8版本,需要更新到1.6.4版 ...
- 清除stoped impdp/expdp job的方法
stoped impdp/expdp job会在dba_datapump_jobs中留下一条记录,显示为not running. 清除stopped job分两种情况: 1) job能够attach ...
- jmeter 插件