c# - C# 中的 `params` 是否总是会导致在每次调用时分配一个新数组?

标签 c# .net variadic-functions

C#/.NET 通过按引用传递 Array 类型来实现可变函数参数(与 C/C++ 不同,C/C++ 只是将所有值直接放在堆栈上,无论好坏).

在 C# 世界中,这有一个很好的优势,即允许您使用“原始”参数或可重用数组实例调用相同的函数:

CultureInfo c = CultureInfo.InvariantCulture;

String formatted0 = String.Format( c, "{0} {1} {2}", 1, 2, 3 );

Int32 third = 3;
String formatted0 = String.Format( c, "{0} {1} {2}", 1, 2, third );

Object[] values = new Object[] { 1, 2, 3 };
String formatted1 = String.Format( c, "{0} {1} {2}", values );

这意味着生成的 CIL 等同于:

String formatted0 = String.Format( c, "{0} {1} {2}", new Object[] { 1, 2, 3 } );

Int32 third = 3;
String formatted0 = String.Format( c, "{0} {1} {2}", new Object[] { 1, 2, third } );

Object[] values = new Object[] { 1, 2, 3 };
String formatted1 = String.Format( c, "{0} {1} {2}", values );

这意味着(在非优化的 JIT 编译器中)每次调用都会分配一个新的 Object[] 实例 - 尽管在第三个示例中您可以将数组存储为字段或其他可重用值,以消除 每次 调用 String.Format 时的新分配。

但在官方的 CLR 运行时和 JIT 中,是否做了任何优化来消除这种分配?或者数组是否经过特殊标记,以便一旦执行离开调用站点的范围,它就会被释放?

或者,也许,因为 C# 或 JIT 编译器知道参数的数量(当使用“原始”时),它可以做与 stackalloc 关键字相同的事情,并将数组放在堆栈上,因此不需要释放它?

最佳答案

是的,每次都会分配一个新的数组。

不,没有进行任何优化。没有你建议的那种“实习”。毕竟,怎么可能呢?接收方法可以对数组做任何事情,包括改变其成员,或重新分配数组条目,或将对数组的引用传递给其他人(然后没有 params)。

不存在您建议的那种特殊“标记”。这些数组以与其他任何东西相同的方式进行垃圾回收。


补充:当然有一种特殊情况,我们在这里讨论的那种“实习”可能很容易做到,那就是长度为零的数组。 C# 编译器可以调用 Array.Empty<T>() (每次都返回相同的长度为零的数组)而不是创建 new T[] { }每当它遇到对 params 的调用时需要长度为零的数组。

这种可能性的原因是长度为零的数组是真正不可变的。

当然可以发现长度为零的数组的“实习”,例如,如果要引入该功能,此类的行为将会改变:

class ParamsArrayKeeper
{
  readonly HashSet<object[]> knownArrays = new HashSet<object[]>(); // reference-equals semantics

  public void NewCall(params object[] arr)
  {
    var isNew = knownArrays.Add(arr);
    Console.WriteLine("Was this params array seen before: " + !isNew);
    Console.WriteLine("Number of instances now kept: " + knownArrays.Count);
  }
}

补充:鉴于 .NET 的“奇怪”数组协变不适用于值类型,您确定您的代码:

Int32[] values = new Int32[ 1, 2, 3 ];
String formatted1 = String.Format( CultureInfo.InvariantCulture, "{0} {1} {2}", values );

按预期工作(如果语法更正为 new[] { 1, 2, 3, } 或类似语法,这肯定会导致错误的重载 String.Format)。

关于c# - C# 中的 `params` 是否总是会导致在每次调用时分配一个新数组?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38679180/

相关文章:

c# - 动态不包含项目引用中属性的定义

java - 我无法从 JSP 页面调用带有 varargs 的方法

c++ - 为什么我的代码无法编译? (完美转发和参数包)

java - 为什么没有使用可变参数的 List 构造函数?

c# - 这段代码是做什么的 : using (SqlConnection cn = new SqlConnection(connectionString))

c# - WCF 和 NetNamedPipeBinding - AVG 防病毒软件

c# - 查询返回 0 行

.net - 是否有一个带有键/值对的通用集合,其中键可以出现多次?

c# - 在 Array 类上使用 Linq 扩展

c# - 在内存、IsoStorage 和服务器之间同步