为什么.NET IL总是创建新的字符串对象,即使更高级别的代码引用现有的对象?

时间:2011-07-13 01:43:40

标签: .net

背景 我们有一个包含数千个伪代码函数的XML文档。我编写了一个实用程序来解析这个文档并从中生成C#代码。这是一个大大简化的代码片段:

public class SomeClass
{
    public string Func1() { return "Some Value"; }
    public string Func2() { return "Some Other Value"; }
    public string Func3() { return "Some Value"; }
    public string Func4() { return "Some Other Value"; }
    // ...
}

重要的内容是每个字符串值可能会被多种方法返回。我假设通过进行一个小的更改,以便方法将返回对静态成员字符串的引用,这将减少程序集大小并减少程序的内存占用。例如:

public class SomeClass
{
    private const string _SOME_VALUE = "Some Value";
    private const string _SOME_OTHER_VALUE = "Some Other Value";
    // ...

    public string Func1() { return _SOME_VALUE; }
    public string Func2() { return _SOME_OTHER_VALUE; }
    public string Func3() { return _SOME_VALUE; }
    public string Func4() { return _SOME_OTHER_VALUE; }
    // ...
}

但令我惊讶的是,使用.NET ildasm.exe 实用程序进行检查表明,在这两种情况下,函数的IL都是相同的。这是其中之一。无论哪种方式,硬编码值都会与 ldstr

一起使用
.method public hidebysig instance string
        Func1() cil managed
{
  // Code size       6 (0x6)
  .maxstack  8
  IL_0000:  ldstr      "Some Value"
  IL_0005:  ret
} // end of method SomeClass::Func1

实际上,“优化”版本稍微差一些,因为它包含程序集中的静态字符串成员。当我使用除字符串之外的其他一些对象类型重复此实验时,我看到了我期望的差异。请注意,在启用优化的情况下生成程序集。

问题: 为什么.NET显然总是创建一个新的字符串对象,无论代码是否引用现有的?

5 个答案:

答案 0 :(得分:6)

  IL_0000:  ldstr      "Some Value"
  IL_0005:  ret

反汇编程序太有帮助了,无法向您展示实际情况。您可以从IL地址判断,注意 ldstr 指令只需要5个字节。太少存储该字符串的方式。使用View + Show标记值来查看它的真实外观。您现在还将看到相同的字符串使用相同的标记值。这被称为'实习'。

令牌值仍然没有显示程序被jitted后字符串的确存储位置。字符串文字进入'loader heap',这是一个与垃圾收集堆不同的堆。它是存储静态项的堆。或者换句话说:字符串文字是高度优化的并且非常便宜。你自己不能做得更好。

答案 1 :(得分:4)

请参阅http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.ldstr(v=vs.71).aspx

  

公共语言基础结构(CLI)保证了结果   两个ldstr指令,指的是两个具有的元数据令牌   相同的字符序列返回相同的字符串对象(a   过程称为“字符串实习”)。

答案 2 :(得分:2)

我现在面前没有Visual Studio,所以我不能给出我想要的简洁答案。您显示的MSIL使其看起来好像字符串不是interned。尝试使用object.ReferenceEquals(...)查看是否确实如此,甚至在文本编辑器中打开已编译的库。如果字符串没有被实习,可能会有一个项目设置来启用实习(再次没有VS在我面前给你一个确切的参考)。

您的另一个选择是将字符串定义更改为static readonly,这应该使方法返回对静态实例的引用。请注意,使用此方法会创建一个隐式静态构造函数,该构造函数将在第一次引用类时创建字符串实例。

答案 3 :(得分:1)

IL代码中的字符串总是如此,因此不会构造新的字符串。您可以使用以下代码验证:

     string str = "123";
     string isinterned = string.IsInterned (str);
     Console.WriteLine(ReferenceEquals(str, isinterned));

常量旨在用作各地的文字(在IL中),而不仅仅是字符串。如果那不是您想要的(我知道一些有效的案例,比如为更新版本的程序集获取更新的“常量值”),请尝试static readonly这样的代替。

public static readonly string _SOME_VALUE = "Some Value";
public static readonly string _SOME_OTHER_VALUE = "Some Other Value";

答案 4 :(得分:-1)

.NET将String对象模拟为基本类型,尽管它是Char数组。在传递给函数时,始终克隆主要类型。因此,在执行任何操作或传递时,.NET将始终克隆String值。