如何确保在使用'params'关键字时更改签名会导致编译错误

9
我有一个像这样的方法:
public void Foo(params string[] args) {
  bar(args[0]); 
  bar(args[1]);
}

新的需求会导致如下更改:
public void Foo(string baz, params string[] args) {
  if("do bar".Equals(baz)) {
    bar(args[0]); 
    bar(args[1]);
  }
}

问题在于,尽管我已更改了方法签名,但没有出现编译错误,这当然是正确的,但我希望每次调用未指定参数bazFoo方法时都会出现编译错误。也就是说,如果在更改之前对Foo的调用是这样的:
Foo(p1,p2); //where p1 and p2 are strings

现在需要的是这个:
Foo(baz,p1,p2);

如果不以这种方式更改,p1将被分配给baz,并且参数数组args的长度为1,将抛出一个OutOfBounds异常。
如何改变签名并确保所有调用代码都相应更新是最好的方式?(真实情况是,Foo位于由许多项目共享的程序集中,在构建服务器上自动构建。编译错误因此是检测需要修改以适应更改的所有代码的简单方法。)
编辑:正如丹尼尔·曼和其他人指出的那样,上面的示例表明我根本不应该使用params。因此,我应该解释一下,在我的现实例子中,并非总是需要args有两个元素,就Foo的逻辑而言,args可以包含任意数量的元素。所以我们假设这是Foo:
public void Foo(string baz, params string[] args) {
  if("do bar".Equals(baz)) {
    int x = GetANumberDynamically();
    for(int i = 0; i<x; i++)
      bar(args[i]); 
  }
}

2
为什么不改变函数的签名以执行完全不同的操作(例如获取一个int或其他类型的数据)。这样所有调用该函数的方法都会产生编译错误。当然,更好的方法是使用Resharper或类似工具进行重构。 - dav_i
补充@dav_i的意见:如果你_首先_添加一个int而不是一个string,编译,修改所有调用代码,然后再将int更改为string。还要注意的是,if("do bar".Equals(baz)) {是一个真正糟糕的例子,听起来像是需要另一个重载或更多的重构。 - CodeCaster
按照 @OndrejJanacek 的建议去做。这绝对是最好的方法。 - dav_i
为了确认一下:您的params参数需要精确地两个输入还是至少两个输入?如果是前者,我建议根本不要使用params... - NobodysNightmare
@dav_i 我不喜欢这种方式的原因是,依赖项目的维护分散在不同的人员手中。如果由于签名中的 int 导致他们的桌面上突然出现构建错误,可能会让人感到困惑,不知道他们需要做什么。我希望出现编译错误,需要一个永久解决方案的修复。但如果这不可行,我认为我会采纳你的建议。 - Manolo
@NobodysNightmare 实际上不是这样的,那只是一个人为制造的例子。我应该解释一下。从Foo的上下文中,我们不知道args参数应该包含多少个params参数才能避免异常。 - Manolo
2个回答

3

这里是解决方案。不要改变以前的方法签名,只需添加具有两个参数指定的Obsolete属性。

[Obsolete("Use Foo(string, params string[]) version instead of this", true)]
public void Foo(params string[] args) {
  bar(args[0]); 
  bar(args[1]);
}

然后创建一个带有新签名的新方法。
public void Foo(string baz, params string[] args) {
  if("do bar".Equals(baz)) {
    bar(args[0]); 
    bar(args[1]);
  }
}
Obsolete属性中的第二个参数可确保编译错误。如果没有这个参数,它只会导致编译警告。有关该属性的更多信息,请参阅MSDN
编辑:根据下面评论中的讨论,Daniel Mann提出了一个有趣的问题。
“那也解决不了问题。如果你调用Foo(“a”,“b”)呢?在这种情况下,它仍将调用只有两个参数的非过时方法,并引起相同的问题。”
我建议在调用bar之前检查是否通过args传递了多个参数。

2
那样做并不能解决问题。如果你调用 Foo("a", "b") 呢?在这种情况下,它仍然会调用只有两个参数的非废弃方法,并导致相同的问题。 - Daniel Mann
是的,它会选择新的重载方法,然后args数组中只有一个项目,这仍然会在提供的示例中导致运行时错误。最初的问题是关于如何避免这种问题的。 - Daniel Mann
是的,但那不是我的问题。这只是一个糟糕的设计,其中 OP 假设将始终通过 args 传递两个参数。 - Ondrej Janacek
1
就你所说的回答并没有真正回答问题,所以这是你的问题。 - Daniel Mann
感谢您的输入。正如指出的那样,这并不能解决给定的示例:使用两个字符串调用Foo函数。 - Manolo
显示剩余3条评论

2
最简单的解决方案是,如果您有必需参数,则不使用params关键字。
显然,您期望args至少包含两个参数。可以说这些都是必需的。为什么不像这样定义方法签名呢? public void Foo(string baz, string requiredArgument1, string requiredArgument2, params string[] optionalArguments) 这消除了歧义:它将始终需要至少3个参数。
我甚至没有想到的另一个选项是使用命名参数。显然,您的所有代码都必须明确指定,但您可以这样做: Foo(baz: "bar", args: new [] {"a", "b", "c"});

关于我的虚拟代码示例,你是完全正确的。不幸的是,在我的真实世界示例中,从Foo的上下文中无法知道需要多少个字符串。(这些参数用于动态加载的查询的参数。) - Manolo
如果Foo处理许多不同的上下文,那么它可能做得太多了?我仍然认为可能需要更全面地重新设计方法签名。此时,也许您可以更详细地说明实际预期的用途。 - Jun Wei Lee
@JunWeiLee 它不能处理不同的上下文,只能处理动态上下文。实际使用情况是,params string[] args 最终成为查询中的参数。查询/参数数量只在运行时才知道。 - Manolo

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