贴了关于.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). :)
毫无疑问:String是引用类型。另一方面,String和int (Int32), Single (float)一样都是.NET的Primitive类型,CLR完全了解String对象的内部构造,并且有内建的用于String操作的指令(ldstr)。String的源代码不是用.NET语言/C#实现的,而是在CLR代码当中。在SSCLI的代码中Object.H里面可以找到下面的结构:
class StringObject : public Object
{
private:
DWORD m_ArrayLength;
DWORD m_StringLength;
WCHAR m_Characters[0];
...
};
另外,.NET程序仍然有办法打破String的Immutable——当然通常不建议这样做:
- unsafe code: C#可以使用unsafe代码直接编辑一个String的字符序列:
fixed (char* p = str) {
p[2] = 'X';
}
- P/Invoke: 传递String作为变量给Native函数的时候,Native函数可以改变String的值。
一直都想当然的接受了“strings are immutable”的事实,倒是没有仔细深入地想过原因。Google了一下,也没有找到满意的答案。我觉得大概有下面这些原因:
- 避免字符串拷贝:如果String的内容可以改变,那么多个对象最好不要保存同一个字符串的引用,否则其中一个改变了String的内容就可能造成程序错误。这在多线程的环境下尤其重要,如果String不是immutable,那么它的所有编辑成员函数(Append, UpperCase等等)都必须要保证县城安全,性能损失惨重。如果每个对象保存String的一份Copy,则会消耗大量内存。Immutable实际上是一种近似于Copy-On-Write的折衷实现。
- 维护集合语义:String是最常用来作为集合(Map,Hashtable) 键值得类型,而一旦一个String对象被用作集合的键值,改变String的内容就会破坏集合的语义,造成程序错误。
- String Interning: 几乎没有人在程序中显式调用过String.Intern方法,但是Interning可能是.NET对String做的最重要的优化。简单的说,CLR为系统中的所有常量字符串维护了一张Hash表,所谓Interning就是在这个Hash表中找到一个动态生成的字符串的等值对象。通常比较两个String是否相等我们需要逐个字符的比较,但是如果两个String都经过Interning处理,因为Hashtable中的String都是唯一的,我们只需要比较这两个String的引用是否相等(是否指向同一个对象)。下面是String.Intern的一个简单的例子:
bool StringEquals(string a, string b) {
string ia = string.Intern(a);
string ib = string.Intern(b);
return Object.ReferenceEquals(ia, ib);
}
C#编译器在两个地方隐含的使用了String Interning: 1) 如果在源代码中多次出现同样的字符串,只有一个对应的String会被放在CLR的StringPool当中,这个String对象会在代码中多次引用到。2) C/C++不支持基于String的switch/case,但是C#支持,这就是通过Intern实现的:C#编译器先把switch对应的字符串进行Interning处理,然后和下面的case进行引用比较就可以了。
显然,String的Intern语义依赖于其不可变性:如果系统Intern Pool中的字符串可能会被改变,CLR就不能隐式的重用这些对象。
无意中发现Google针对几个Term的特殊搜索,包括Microsoft、Apple、BSD和Linux。
查找Microsoft相关内容的时候msdn,gotdotnet等等网站的Page Ranking会被提高,这样更容易找到相关的信息。以后查找东西的时候就不用加上MSDN或者site:msdn.microsoft.com做关键字了。:)
上帝说的,有问题,找GOOGLE !!
为回答一个CSDN上的问题Google了半天,居然没能找到一个像样的C#访问Structure Storage的例子。没办法,自己动手丰衣足食了。:)
用C#作Structure Storage最大的麻烦是无数的COM Interface和API调用,实在没有心情把一堆COM接口函数都写出来了,只写了用到的几个: StgOpenStorageEx、IPropertySetStorage::Open、IPropertyStorage::ReadMultiple,而且功能也不全,只是取得Document的Creating Application属性,但是可以解决上面的问题了。(下载,没有找到合适的上传空间,随手申请了一个Free的)
如何判断一个文档是否是office文档????
FROM MY REPLY:
Office文件都是使用结构化存储复合文件(Structured Storage, Compound File),可以从它们的文件属性里面取得创建的应用程序,例如,在文件属性对话框当中的Summary一页有定义Application Name,可以看到文件使用Excel或者Word创建的。
但是在dotNet程序读取这些信息比较麻烦:
1)需要声明StgOpenStorageEx函数,包括参数的结构和常量定义。
2) 需要声明IPropertySetStorage,IPropertyStorage和IEnumSTATPROPSTG COM接口。
3) 定义Summary Information property set的FMTID:F29F85E0-4FF9-1068-AB91-08002B27B3D9。
4)通过StgOpenStorageEx打开文件,取得IPropertySetStorage.
5)用ReadMultiple读取Property ID = 0x12 (Creating Application),如果名字是Excel或者Word,那么是Office文件。