可变数量的参数没有装箱值类型?

public void DoSomething(params object[] args) { // ... } 

上述签名的问题在于,将传递给该方法的每个值类型都将被隐式装箱,这对我来说是严重的性能问题。

有没有办法去掉一个方法,接受可变数量的参数而不装箱值类型?

谢谢。

你可以使用generics:

 public void DoSomething(params T[] args) { } 

但是,这只允许指定单一类型的ValueType。 如果您需要混合或匹配值类型,则必须允许装箱,就像您现在所做的那样,或者为不同数量的参数提供特定的重载。


编辑:如果您需要多种类型的参数,可以在某种程度上使用重载来完成此操作。

 public void DoSomething(T arg1, params U[] args) {} public void DoSomething(T arg1, T arg2, params U[] args) {} 

不幸的是,这需要为您的类型存在多个重载。

或者,您可以直接传入数组:

 public void DoSomething(T[] args1, U[] args2) {} 

你失去了很好的编译器语法,但是你可以传递任意数量的两个参数。

目前还没有,没有,我还没有看到任何解决已发布的.NET 4信息中的问题。

如果这对您来说是一个巨大的性能问题,您可能会考虑常见参数列表的几个重载。

我想知道:它真的是一个性能问题,还是你过早地优化了?

让我们假设您调用此方法的代码知道参数类型。 如果是这样,您可以将它们从.NET 4打包到适当的Tuple类型中,并将其实例(Tuple是引用类型)传递给对象这样的方法(因为所有元组都没有公共基础)。

这里的主要问题是在没有装箱/拆箱的情况下处理此方法内的参数并不容易,甚至可能没有reflection。 尝试思考必须要做什么来提取,比方说,没有拳击的第N个参数。 你最终会理解你必须处理那里的字典查找(包括常规Dictionary或CLR使用的内部字典 ),或者使用装箱。 显然,字典查找的成本要高得多。

我写这个是因为实际上我们为非常类似的问题开发了一个解决方案:我们必须能够在没有装箱的情况下使用我们自己的元组进行操作 – 主要是对它们进行比较和反序列化(元组由我们开发的数据库引擎使用,所以任何性能在我们的案例中,基本操作非常重要)。

但:

  • 我们最终得到了非常复杂的解决方案 看看例如TupleComparer 。
  • 没有拳击的效果实际上不如我们预期的那么好:每个装箱/拆箱操作都被单个数组索引和几个虚方法调用所取代,两种方式的成本几乎相同。

我们开发的方法的唯一好处是我们不会通过垃圾“泛滥”Gen0,因此Gen0集合很少发生。 由于Gen0收集成本与“实时”对象分配的空间及其计数成比例,这带来了明显的优势,如果其他分配与算法执行混合(或仅仅发生),我们尝试通过这种方式进行优化。

结果:经过优化后,我们的综合测试显示性能提高0%至200-300%; 另一方面,数据库引擎本身的简单性能测试显示出不太令人印象深刻的改进(约5-10%)。 很多时间浪费在上面的层次(也有一个非常复杂的ORM),但是…很可能这是你在实现类似的东西之后真正看到的。

简而言之,我建议你专注于其他事情。 如果完全清楚这是您的应用程序中的主要性能问题,并且没有其他好的方法可以解决它,那么,继续… 否则您只是通过过早优化从您的客户或您自己的钢铁

对于完全通用的实现,常见的解决方法是使用流畅的模式。 像这样的东西:

 public class ClassThatDoes { public ClassThatDoes DoSomething(T arg) where T : struct { // process return this; } } 

现在你打电话:

 classThatDoes.DoSomething(1).DoSomething(1m).DoSomething(DateTime.Now)//and so on 

但是这对静态类不起作用(扩展方法没问题,因为你可以返回this )。

你的问题与此基本相同: 我可以拥有可变数量的通用参数吗? 以不同的方式问道。

或者使用params关键字接受一系列项目:

 public ClassThatDoes DoSomething(params T[] arg) where T : struct { // process return this; } 

并致电:

 classThatDoes.DoSomething(1, 2, 3) .DoSomething(1m, 2m, 3m) .DoSomething(DateTime.Now) //etc 

创建开销的数组是否小于拳击开销是你必须自己决定的。

在C#4.0中,您可以使用命名(因此可选)参数! 有关此博客文章的更多信息