为什么存在String.Format的重载?

18
我使用反射器查看String.Format方法的实现,并一直认为只有一个、两个和三个参数的String.Format重载是该方法的优化版本,它们比接受对象数组参数的版本更高效。然而,我发现内部实际上它们会创建一个对象数组,然后调用一个接受对象数组参数的方法。 1个参数:
public static string Format(string format, object arg0)
{
    if (format == null)
    {
        throw new ArgumentNullException("format");
    }
    return Format(null, format, new object[] { arg0 });
}

2个参数

public static string Format(string format, object arg0, object arg1)
{
    if (format == null)
    {
        throw new ArgumentNullException("format");
    }
    return Format(null, format, new object[] { arg0, arg1 });
}

3个参数

public static string Format(string format, object arg0, object arg1, object arg2)
{
    if (format == null)
    {
        throw new ArgumentNullException("format");
    }
    return Format(null, format, new object[] { arg0, arg1, arg2 });
}

对象数组

public static string Format(string format, params object[] args)
{
    if ((format == null) || (args == null))
    {
        throw new ArgumentNullException((format == null) ? "format" : "args");
    }
    return Format(null, format, args);
}

实际上它们最终都使用相同的代码,因此使用1、2和3个参数版本并不比使用对象数组版本更快。

所以我的问题是 - 为什么它们存在?

当您使用逗号分隔的值列表的对象数组版本时,编译器会自动将参数转换为对象数组,这是由于params/ParamArray关键字所致,这本质上就是1、2和3个版本所做的,因此它们似乎是冗余的。那么BCL设计者为什么要添加这些重载呢?

2个回答

8

正如Hans提到的那样,创建数组在大多数常见的字符串格式化情况下是很多不必要的开销。这可以节省EXE的空间。

另一个原因是,并非所有语言都支持可变参数函数(在C#中使用params)。这使得那些语言的用户可以避免在最常见的字符串格式化情况下创建数组。对于没有简单语法进行数组创建和初始化的语言来说,这可以节省很多空间。


创建数组是很多不必要的开销。数组是在方法内部创建的,这在“开销”方面有何不同? - Chris Marisic
2
@ChrisMarisic:问题不在于创建数组的内存使用或GC开销,而在于额外的操作码。仅使用单个字符串替换调用string.Format只需要3个IL操作码,而如果不存在重载,则需要10个操作码。每个附加参数仅需要1个操作码,如果可以使用重载,则需要4个操作码。由于如果方法太多操作码就无法内联,因此这具有重大影响。 - Gabe
所以重载是为了增加 Format 被调用的地方内联的概率吗?如果必须使用 params 重载,它永远不会被内联吗? - Chris Marisic
重载可以增加包含“Format”调用的方法被内联的概率。 - Gabe

3

您忘记了应用程序中需要进行呼叫的代码。创建数组并填充它所需的IL比仅传递3个参数要多得多。


你说得完全正确。我没有考虑到那一点。我想你可以说这些方法优化了调用代码的大小,而不是性能,这是我一直以为它们存在的原因。 - John Mills
那么,如果您在直接IL中调用它,重载就有意义了吗? - Chris Marisic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接