C# 使用插值vs";连接字符串的内存使用情况+&引用;操作人员

C# 使用插值vs";连接字符串的内存使用情况+&引用;操作人员,c#,string,string-concatenation,C#,String,String Concatenation,就可读性而言,我看到了使用插值字符串的好处: string myString = $"Hello { person.FirstName } { person.LastName }!" 通过以下方式进行连接: string myString = "Hello " + person.FirstName + " " person.LastName + "!"; 该书的作者声称,第一本书能更好地利用记忆 为什么呢?作者实际上并没有说一个比另一个更好地利用内存。它说一种方法在抽象上“很好地利用了记忆”

就可读性而言,我看到了使用插值字符串的好处:

string myString = $"Hello { person.FirstName } { person.LastName }!"
通过以下方式进行连接:

string myString = "Hello " + person.FirstName + " " person.LastName + "!";
该书的作者声称,第一本书能更好地利用记忆


为什么呢?

作者实际上并没有说一个比另一个更好地利用内存。它说一种方法在抽象上“很好地利用了记忆”,这本身并不意味着什么


但不管他们怎么说,这两种方法在实现上并没有什么明显的不同。两者在内存或时间方面都不会有明显的区别。

字符串是不可变的。这意味着他们无法改变

当用+号连接字符串时,最终会创建多个字符串以获得最终字符串

当您使用插值方法(或StringBuilder)时,.NET运行时会优化字符串的使用,因此它(理论上)具有更好的内存使用率

尽管如此,这通常取决于你在做什么,以及你多久做一次

一组串联并不能提供很多性能/内存改进

在循环中进行这些连接可以有很大的改进。

因为c#中的字符串是不可变的,这就是为什么同一个内存会被反复使用的原因,所以它不会对内存产生太大的影响,但是在性能方面,您实际上区分了
String.Format
String.Concat
,因为在编译时代码将是这样的

  string a = "abc";
  string b = "def";

  string.Format("Hello {0} {1}!", a, b);

  string.Concat(new string[] { "Hello ", a, " ", b, "!" });

如果您感兴趣,这两者之间有一个关于性能的完整线索

我做了一个简单的测试,见下文。如果连接常量,不要使用“string.Concat”,因为编译器无法在编译时连接字符串。如果使用变量,结果实际上是相同的

时间测量结果:

const string interpolation : 4
const string concatenation : 58
const string addition      : 3
var string interpolation   : 53
var string concatenation   : 55
var string addition        : 55
mixed string interpolation : 47
mixed string concatenation : 53
mixed string addition      : 42
守则:

void Main()
{

const int repetitions = 1000000; 
const string part1 = "Part 1"; 
const string part2 = "Part 2"; 
const string part3 = "Part 3"; 
var vPart1 = GetPart(1); 
var vPart2 = GetPart(2); 
var vPart3 = GetPart(3); 

Test("const string interpolation ", () => $"{part1}{part2}{part3}"); 
Test("const string concatenation ", () => string.Concat(part1, part2, part3)); 
Test("const string addition      ", () => part1 + part2 + part3); 
Test("var string interpolation   ", () => $"{vPart1}{vPart2}{vPart3}"); 
Test("var string concatenation   ", () => string.Concat(vPart1, vPart2, vPart3)); 
Test("var string addition        ", () => vPart1 + vPart2 + vPart3); 
Test("mixed string interpolation ", () => $"{vPart1}{part2}{part3}");
Test("mixed string concatenation ", () => string.Concat(vPart1, part2, part3));
Test("mixed string addition      ", () => vPart1 + part2 + part3);

void Test(string info, Func<string> action) 
{ 
    var watch = Stopwatch.StartNew(); 
    for (var i = 0; i < repetitions; i++) 
    { 
        action(); 
    } 
    watch.Stop(); 
    Trace.WriteLine($"{info}: {watch.ElapsedMilliseconds}"); 
} 

string GetPart(int index) 
    => $"Part{index}"; 

}
void Main()
{
常量整数重复次数=1000000;
常量字符串part1=“part1”;
常量字符串part2=“part2”;
常量字符串part3=“part3”;
var vPart1=获取部分(1);
var vPart2=获取部分(2);
var vPart3=获取部分(3);
测试(“常量字符串插值”,()=>$”{part1}{part2}{part3}”);
测试(“常量字符串连接”,()=>string.Concat(第1部分、第2部分、第3部分));
测试(“常量字符串加法”,()=>part1+part2+part3);
测试(“变量字符串插值”,()=>$”{vPart1}{vPart2}{vPart3}”);
测试(“变量字符串连接”,()=>string.Concat(vPart1、vPart2、vPart3));
测试(“var字符串加法”,()=>vPart1+vPart2+vPart3);
测试(“混合字符串插值”,()=>$“{vPart1}{part2}{part3}”);
测试(“混合字符串连接”,()=>string.Concat(vPart1,part2,part3));
测试(“混合字符串加法”,()=>vPart1+part2+part3);
无效测试(字符串信息、函数操作)
{ 
var watch=Stopwatch.StartNew();
对于(变量i=0;i<重复;i++)
{ 
动作();
} 
看,停;
Trace.WriteLine($“{info}:{watch.elapsedmillesons}”);
} 
字符串GetPart(int索引)
=>$“部分{index}”;
}

除非这是一个紧密循环的一部分,否则您真的认为这里的内存使用情况会有显著差异吗?选择编写对您来说读起来最干净的代码,如果您确实关心内存使用情况,请分析代码以查找实际热点。“互联网上有人告诉我”是一种糟糕的性能决策方式。我没想到内存使用会有巨大的差异:)我试图理解底层实现的差异,我认为它们是严格等效的方法!对于非常少的参数,字符串连接速度稍快,但需要更多内存。超过20个参数后,concat在时间和内存方面都更好。在所示的两个示例中都不会创建中间字符串。由于在编译时有固定数量的字符串,所以这两个字符串都能够生成一个最终字符串,因此连接将能够将它们全部连接在一起。这里显示的两种方法都是如此,而不仅仅是在使用字符串插值时。在这里使用
StringBuilder
会比前面介绍的任何一个选项都慢,并且使用更多的内存。这不是关于内存消耗和计时的问题吗?除非我对字符串和字符串池的理解不准确,否则这两种方法是有意义的不同。示例:A=“X”,B=“Y”,C=“Z”。现在字符串池中已经有3个字符串。A+B+C还将以下内容添加到字符串池中:“XY”、“XYZ”。但是,$“{A}{B}{C}”仅添加“XYZ”。少一个从未使用过的是“XY”。现在把它放在一个大的数据集或循环中,或者两者都放在一起,注意这个例子很简单,在现实世界中的应用程序中的差异是指数级的。(再说一次,除非我的理解有缺陷,如果有,我愿意接受教育,请接受教育。)@MichaelPuckettII你关于大量中间字符串被创建的假设完全是错误的。所有5个底层字符串都在开始时求值,并创建一个新字符串,因为所有被合并的字符串在编译时都是已知的。如果代码是完全不同的,在使用循环的地方,那么是的,它的行为会不同。但是,由于讨论中的两个代码段都没有这样做,所以与这个问题无关。@MichaelPuckettII另外,在运行时创建的字符串不会被保留。intern池中唯一的字符串是编译时常量。(除非您明确调用
string.Intern
,这里显然没有这样做。)谢谢您的解释。我想更详细地了解这一点。你有什么可以给我指点的资源吗?我指定了,除非我的理解是错误的,并且没有说你错了。我让自己接受教育和批评。我现在可以从你的评论中看出你就是其中之一。祝你愉快