c# - string.Concat 11 次重载的原因

标签 c# string

我刚刚注意到该方法有 11 个重载 string.Concat()

public static string Concat(IEnumerable<string> values);
public static string Concat<T>(IEnumerable<T> values);
public static string Concat(object arg0);
public static string Concat(params object[] args);
public static string Concat(params string[] values);
public static string Concat(object arg0, object arg1);
public static string Concat(string str0, string str1);
public static string Concat(object arg0, object arg1, object arg2);
public static string Concat(string str0, string str1, string str2);
public static string Concat(object arg0, object arg1, object arg2, object arg3);
public static string Concat(string str0, string str1, string str2, string str3);

这是什么原因?两者
public static string Concat(params object[] args);
public static string Concat<T>(IEnumerable<T> values);

应该是唯一需要的,因为它们同样方便/强大。 MSDN对此没有给出答案,如果您从框架中删除 9 个“重复”重载,则没有人会注意到。

最佳答案

此实现决策的主要动机是性能。

正如您正确指出的那样,可能只有两个:

public static string Concat(params object[] args);
public static string Concat<T>(IEnumerable<T> values);

如果 C# 实现了“params enumerable”特性——可变参数方法可以有一个 IEnumerable<T>而不是 T[]作为扩展参数——那么我们可以只得到一个。或者,我们可能会丢失可枚举重载而只使用对象数组版本。

假设我们做了后者。你说
string x = Foo();
string y = Bar();
string z = x + y;

会发生什么?在只有可变参数的世界中 ToString这只能编码为
string x = Foo();
string y = Bar();
object[] array = new string[2];
array[0] = x;
array[1] = y;
string z = string.Concat(array);

所以:让我们回顾一下。大概每个调用分配一个字符串。然后我们分配一个短期数组,将引用复制到它,将它传递给可变参数方法,等等。需要编写该方法来处理任意大小的数组,处理空数组,等等。

我们不仅向零代堆添加了新的短期垃圾;我们还在活性分析图中创建了两个可能需要遍历的新边。我们可能通过增加压力减少了收集之间的时间,或者通过增加边缘增加了收集的成本,或者,最有可能的是,收集变得更加频繁和更加昂贵:双重打击。

但是等等,还有更多。我们必须考虑调用 Concat 的实现是什么方法看起来像。

对象数组是——出人意料——对象数组,而不是字符串数组。那么我们需要做什么呢?被调用者需要将每个转换为字符串。调用 ToString在各个?不,那可能会崩溃。首先检查 null,然后调用 ToString .

我们已经传入了字符串,但被调用者不知道这一点。 ToString是字符串的标识,但 编译器不知道 ,并且调用是虚拟化的,因此抖动也不能轻易优化掉。因此,我们又进行了一些不必要的几纳秒检查和间接访问。更不用说我们需要检查数组是否为空,获取数组的长度,创建一个循环来遍历数组的每个元素,等等。

这些开销非常小,但它们是每个级联的,它们可能会增加实际花费的时间和内存压力。

许多程序的性能取决于字符串操作和内存压力。我们如何消除或减轻这些成本?

我们可以观察到大多数字符串连接是两个字符串,因此创建一个专门用于处理这种情况的重载是有意义的:
static string Concat(string, string)

现在我们可以将上面的片段编码为:
string x = Foo();
string y = Bar();
string z = string.Concat(x, y);

现在没有创建数组,因此没有创建额外的垃圾,没有收集压力,引用图中没有新的边。在被调用方中,需要检查字符串是否为空,但我们不需要调用 ToString在实现中,因为我们有类型系统来强制操作数已经是字符串,我们不需要检查数组是否为空,我们不需要根据数组长度检查循环变量,等等。

因此,我们有一个很好的理由来进行两个重载:一个采用 params 数组,另一个采用恰好两个字符串。

现在我们对另一种常见且性能更高的场景重复该分析。每个额外的重载旨在为常见场景生成更有效的替代方案。随着更常见的场景被识别出来,可以变得更快,资源占用更少,就有动力产生更多的重载,并修复编译器,以便它们生成利用这些重载的代码。最终结果是大约十几个看似多余的过载,每个过载都可以针对高性能进行调整;这些涵盖了实际程序中最常见的情况。

如果您对这个主题感兴趣,我已经写了一系列关于我如何在 2006 年重新设计字符串连接优化器的短篇文章。

https://ericlippert.com/2013/06/17/string-concatenation-behind-the-scenes-part-one/

关于c# - string.Concat 11 次重载的原因,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40528758/

相关文章:

c# - 将样式添加到 GridViewColumnHeader 后,不再允许调整 GridView 列的大小

c# - 从 foreach 循环中将模型加载到局部 View 中

java - 使用带有继承的 Jackson 将字符串映射到对象

java - 根据android中的一些特殊字符将一个字符串一分为二

java - 在 Java 中替换数据的最快方法

c# - 使用 C# 以编程方式同步两个数据库

c# - NetworkStream 从不写入数据

c# - 为什么从 C# 程序调用 C 函数不需要不安全上下文?

php - 如何在 explode 和替换期间删除逗号空格?

c++ - 如何用常量字符替换字符串的子字符串,如 'X'