贴了关于.NET String Immutable和Implementaion的blog,收到下面两个回复:
- 对于定义如 public void xx(string yy);的函数,传进去的yy的值是改不了的.
下面的程序展示了如何在Native函数中修改字符串参数的值:
//Native.cpp: the exported native dll function
extern "C" _declspec(dllexport) void _stdcall ModifyString(LPWSTR str) {
str[0] = L'X';
}
//Test.cs: Csharp function:
[DllImport(“Native.dll”, CharSet=CharSet.Unicode)]
public static extern void ModifyString(string str);
public static void Main() {
string str = “Hello, World”;
ModifyString(str);
Console.WriteLine(str);
}
这里的关键是Unicode,.NET String的内部编码是UCS2,如果Native函数参数恰好用的是UCS2,出于性能考虑,CLR会直接把string内部字符串的地址传给Native;如果Native函数接受ASCII编码,CLR就不得不做一个拷贝了。
- Hash算法从来都不保证不同的值生成的Hash值不同!
正是因为散列函数可能出现冲突,所以在散列查找之后还必须比较键值以确定查找的正确性,所以一旦加入Hashtable,对象的键值是不能改变的。下面的程序用CLR内部的InternTable来展示改变一个Intern字符串的结果:
static unsafe void ModifyConst() {
string str = "Hello";
fixed(char* pstr = str) {
pstr[0] = 'X';
}
}
static void Main() {
ModifyConst();
StringBuilder sb = new StringBuilder("Hel");
sb.Append("lo");
string str = sb.ToString();
Console.WriteLine(str);
switch(str) {
case "Xello":
Console.WriteLine("string is Xello"); break;
case "Hello":
Console.WriteLine("string is Hello"); break;
default:
Console.WriteLine("Not Found"); break;
}
}
不妨猜猜看程序的输出是什么。(Warning: Think at your own risk. Potential side effects include dry mouth, sleepless and mind blow). :)
打印 | 张贴于 2004-08-23 04:22:00 | Tag:.NET
留言反馈
2. String传值还是传引用
3. string和String有什么区别?
4. String为什么是Immutable,怎么实现的?
{
string str = "Hello";
fixed(char* pstr = str)
{
pstr[0] = 'X';
}
}
private void button2_Click(object sender, System.EventArgs e)
{
string str = "Hel" + "lo";
string internStr = string.IsInterned(str);
if (internStr != null)
this.listBox1.Items.Add(internStr);
else
this.listBox1.Items.Add("NULL");
}
private void button3_Click(object sender, System.EventArgs e)
{
StringBuilder sb = new StringBuilder("Hel");
sb.Append("lo");
string str = sb.ToString();
this.listBox1.Items.Clear();
this.listBox1.Items.Add(str);
switch(str)
{
case "Xello":
this.listBox1.Items.Add("string is Xello"); break;
case "Hello":
this.listBox1.Items.Add("string is Hello"); break;
default:
this.listBox1.Items.Add("Not Found");
this.listBox1.Items.Add("\tBut: " + string.IsInterned(str));
break;
}
}
private void button4_Click(object sender, System.EventArgs e)
{
ModifyConst();
// 问题在于有没有以下一句,情势完全不同。
// button2 和 button3 的 Click 处理结果完全不一样。
// 小弟愚昧,不知其所以然,还望指点。
string.Intern("Hello");
}
我在一个 WINFORM 上测试本帖示例代码时,作了以上修改,但出现的结果实令人费解.
JGTM's explanation is right. What happened is that the new string "Hello" matches the hashcode of const "Hello" whose value is changed into "Xello". Because the string values are not matched, however, the new string is added to the intern pool and it's interned reference doesn't match any of the strings in "cases".
老大,那么是否是compile时候维护的这个Hashtable呢?还是runtime,第一次JIT这个方法的时候?
请指教,呵呵,没找到这方面的资料。
i5==i6那是自然!因为i6的来源"Hello"也是常量,和s1-s3一样已经在内部被改成了"Xello",求出hash当然和原封不动的"Xello"一样咯。
我所说的第一句话的关键在于编译器对switch/case语句的编译期处理——如果case "Hello"的hash是在编译期求得的,则在运行期就会出现switch的目标与case的目标的hash不同然而键值相同的情形。
鼓励怀疑(比如说编译器倒底是不是如我所说的那样去处理入口为string时的switch/case呢?),但不要轻易不同意……对我没什么坏处,对你自己不一定好。;-)
string s1 = "Hello";
int i1 = s1.GetHashCode();
string s2 = "Hello";
int i2 = s2.GetHashCode();
string s3 = s2;
int i3 = s3.GetHashCode();
ModifyConst(s1);
i1 = s1.GetHashCode();
i2 = s2.GetHashCode();
i3 = s3.GetHashCode();
StringBuilder sb = new StringBuilder("Hel");
sb.Append("lo");
string str = sb.ToString();
int i4 = str.GetHashCode();
Console.WriteLine(str);
// string s = string.Intern("Hello");
// Console.WriteLine(s);
int i5 = "Xello".GetHashCode();
int i6 = "Hello".GetHashCode();
运行一下这段代码,i5==i6,他们的hash是相同的。
原因正如知秋一叶所说的“在散列查找之后还必须比较键值以确定查找的正确性”(事实上任何基于散列的查找都必须保证这一点——包括说在SQL Server中使用checksum(A)=checksum(@x)来提高查询速度一样,别忘了再AND一个A=@x……):ModifyConst()里面的"Hello"常量和case "Hello"里面的常量在编译期间(这个我还拿不太准——constants' interning是发生在compile-time还是runtime呢?)被interned为一个入口,同时switch(str)中的所有case也被求出hash以供运行期匹配;然而在运行期间内存中的这个interned "Hello"被改为了"Xello",但是switch语句还是以运行期生成的"Hello"的hash作为匹配依据找到了case "Hello"这一入口(因为无论是compile-time还是runtime,"Hello"这5个字母所组成的字符串的hash都是相等的),但是随后的键值比较发现运行期生成的这个“Hello”和case "Hello"这个入口实际已经变成"Xello"的键值并不相等,于是只能fallback to default case输出"Not Found"了。
为了更好的理解CLR的interning机制,不妨再在程序最后补上一句Console.WriteLine("Hello");,看看运行期到底发生了什么变化。