为什么要使用 params 关键字?

361

我知道这是一个基础问题,但我找不到答案。

为什么要使用它?如果您编写一个使用它的函数或方法,并且在删除它时代码仍然可以完美地工作,与没有使用它时效果一样。例如:

使用参数:

static public int addTwoEach(params int[] args)
{
    int sum = 0;
    foreach (var item in args)
        sum += item + 2;
    return sum;
}

没有参数:

static public int addTwoEach(int[] args)
{
    int sum = 0;
    foreach (var item in args)
       sum += item + 2;
    return sum;
}

66
这个方法的代码本身仍将完美运行……但是调用该方法的代码可能不会。 - Jon Skeet
8
参数关键字表示可以选择性地传递给方法的参数。没有参数关键字的数组意味着您必须向该方法传递数组参数。 - Ailayna Entarria
Python语言通过在参数前加星号(*)实现了与此处所提到的相同的概念,非常优雅。具体请参考此处 - RBT
11个回答

519

使用 params,您可以像这样调用方法:

addTwoEach(1, 2, 3, 4, 5);

没有 params,你无法做到。

此外,在两种情况下都可以使用数组作为参数调用该方法:

addTwoEach(new int[] { 1, 2, 3, 4, 5 });

也就是说,使用params可以让你在调用该方法时使用快捷方式。

无关的话,你可以大幅缩短你的方法:

public static int addTwoEach(params int[] args)
{
    return args.Sum() + 2 * args.Length;
}

18
@Ken: 你可能需要导入 "System.Linq" 命名空间 :) - Ranhiru Jude Cooray
55
或者返回 args.Sum(i => i + 2); - bornfromanegg
3
代理对象的使用确实会增加编译代码的复杂度,这可能会降低性能。但在此特定情况下并不相关,因为它不会导致任何闭包,但了解编译器实际执行的操作仍然很值得。 - Allen Clark Copeland Jr
2
你也可以使用 return args.Select(x => x + 2).Sum(); - bbvg
不知道C#有Python的*args的等效物!太喜欢了。注意:如果该方法有多个参数,则params参数必须是列表中的最后一个。 - Emil
1
当您添加params时,您会封锁自己,无法添加其他方法参数,而不会破坏调用者或方法解析。 - Mr. Young

104

使用 params 参数允许您在调用函数时不传递参数。没有 params

static public int addTwoEach(int[] args)
{
    int sum = 0;

    foreach (var item in args)
    {
        sum += item + 2;
    }

    return sum;
}

addtwoEach(); // throws an error

params 相比较:

static public int addTwoEach(params int[] args)
{
    int sum = 0;

    foreach (var item in args)
    {
        sum += item + 2;
    }

    return sum;
}

addtwoEach(); // returns 0

通常,当参数数量从0到无限变化时,您可以使用params关键字,而当参数数量从1到无限变化时,您可以使用数组。


13
实际上,一个数组可以是空的。new int[0]。希望这可以帮助你理解! :) - vidstige

22

它允许您在调用时添加任意多个基本类型参数。

addTwoEach(10, 2, 4, 6)

与第二种形式不同的是,你必须使用一个数组作为参数。

addTwoEach(new int[] {10,2,4,6})

21
params 关键字存在一个危险,即在已编码的方法调用之后:
  1. 有人不小心或故意从方法签名中删除了一个或多个必需的参数,并且
  2. params 参数之前的一个或多个必需的参数与 params 参数的类型兼容。
那么这些调用将继续编译,其中原本用于必需的参数的一个或多个表达式将被视为可选的 params 参数。我刚刚遇到了最糟糕的情况: params 参数的类型为 object[]
这值得注意,因为开发人员习惯于在方法中删除所有 必需的 参数(因为期望的参数数量会改变)时,编译器会产生更常见的情况,即对其进行警告。
对我来说,这种捷径不值得冒险。没有 params(Type)[] 可以在不需要重载的情况下使用 0 到无限数量的参数。最坏的情况是,您必须在不适用的调用中添加 , new (Type) [] {}
另外,我认为最安全(也最易读)的做法是:
  1. 通过具名参数传递(即使在 C# 中,这也是我们可以在 VB 之后的两个十年中实现的 ;P),因为:
  2. 1.1. 这是唯一确保在已编码的调用之后防止意外值传递到参数的方法,即使参数顺序、兼容类型和/或计数发生变化。

    1.2. 它在参数含义更改后减少了这些可能性,因为反映新含义的新标识符名称就在要传递的值旁边。

1.3. 它避免了计算逗号和从调用到签名来回跳转以查看为哪个参数传递表达式的情况,并且

1.3.1. 顺便说一句,仅凭这个原因就应该足够(避免频繁违反 DRY 原则的错误,仅仅是为了阅读代码而不用提及修改它),但如果有一个/多个包含逗号的表达式被传递,即多维数组引用或多参数函数调用,则这个原因可以呈指数级增长。在这种情况下,您甚至无法使用(即使您可以,它仍然会为每个参数每次方法调用添加一个额外的步骤)编辑器中的“在选择中查找所有出现”功能来自动执行逗号计数。

1.4. 如果必须使用可选参数(params 或非 params),它允许您搜索调用其中传递了特定可选参数的情况(因此,很可能不是默认值或至少有可能不是默认值)。

(注意:原因 1.2. 和 1.3. 可以简化和降低出错的几率,即使在编写初始调用时也是如此,更不用说需要阅读和/或更改调用的情况了。)

  1. 为了提高可读性,每行仅进行一个参数传递(因为:

    2.1. 它不会那么杂乱无序,

    2.2. 它避免了左右滚动(并且必须针对每一行这样做,因为大多数普通人无法读取多行的左侧部分,然后滚动到右侧部分进行阅读)。

    2.3. 这与我们已经发展成为赋值语句的“最佳实践”是一致的,因为每个传递的参数本质上都是一个赋值语句(将值或引用分配给本地变量)。就像那些遵循最新“最佳实践”编码样式的人不会在一行中编写多个赋值语句一样,当传递参数时,我们可能不应该这样做(并且一旦“最佳实践”达到我的“天才”水平时,也不会这样做)。

  1. 当:

    1.1. 您正在传递文字常量(即简单的0/1、false/true或null,即使“最佳实践”可能不要求您使用命名常量,并且它们的目的不能从方法名称轻松推断出来),

    1.2. 该方法比调用方更低级/更通用,因此您不想/无法将变量的名称与参数相同/相似(反之亦然),或者

    1.3. 您正在重新排序/替换签名中的参数,这可能导致先前的调用仍然编译,因为类型恰好仍然兼容。

    在这些情况下,传递名称与参数相同的变量将无济于事。

  2. 像VS一样具有自动换行功能只消除了我上面提到的8个理由中的一个(#2.2)。 在最新的VS 2015之前,它没有自动缩进(?!真的,微软?!),这增加了#2.2原因的严重性。

应该有一个选项在VS中生成以命名参数(每行一个)的方法调用片段,并且编译器应该有一个选项要求使用命名参数(类似于VB中的“Option Explicit”,顺便提一下,当时这种要求可能被认为同样荒唐,但现在可能已经被“最佳实践”所要求)。事实上,“在我的时代”,即1991年我刚开始工作的几个月,甚至在我使用(或甚至看到)具有命名参数的语言之前,我就能够用内联注释模拟它(使用每行注释)。不必使用命名参数(以及其他保存“珍贵”源代码击键的语法)是时代洪流退去的产物,大多数这些语法都起源于打孔卡时代。在拥有现代硬件和IDE以及更复杂的软件的今天,其中可读性要重要得多、多得多。“代码通常比写的更多地被阅读”。只要不复制非自动更新的代码,每节省的按键可能会在稍后某人(甚至你自己)尝试阅读时成倍增加成本。


2
我不明白。为什么你不能强制要求至少有一个参数?即使没有参数,也没有阻止你将nullnew object [0]作为参数传递。 - Casey
在可选参数之前有必需的参数可能会太危险了(万一调用代码后删除了其中一个或多个必需参数)。这可能是为什么我从未在任何关于可选参数的文档中看到过示例代码中有必需参数在可选参数之前的原因。顺便说一句,我认为最安全(也是最易读的做法)是通过命名参数传递(即使在C#中我们可以做到这一点,但在VB中已经有20年了)。VS应该有一个选项,可以生成带有命名参数的方法调用片段(并且每行只有1个参数)。 - Tom
我不太确定你的意思。唯一可以同时拥有必需参数和可选参数的方法是先指定所有必需参数。 - Casey
1
我将myMethod声明为void myMethod(int requiredInt, params int[] optionalInts)。我/其他人编写了一个或多个调用,例如myMethod(1)myMethod(1, 21)myMethod(1, 21, 22)。我将myMethod更改为void myMethod(params int[] optionalInts)。尽管这些调用的第一个参数(即“1”)显然不是作为optionalInts参数的第一个元素传递的,但所有这些调用仍将编译而没有错误。 - Tom
哦,好的,在那种特殊情况下可能不太明智。如果你需要一个字符串和0到多个整数或其他内容,我认为没有理由避免使用它。 - Casey
@Casey:关于“如果你需要一个字符串和0到多个整数或其他内容,我认为没有任何理由避免使用它。”的问题:通常情况下是没有理由避免使用的。这就是为什么我说“危险”只适用于“所需参数与params参数类型兼容”的情况。尽管经过进一步考虑,即使在这种情况下,即使在所有必需参数之前和之后都进行更改的方法中,仍然有办法更改方法签名,其中现有调用仍将编译(传递给意外参数的值)。最安全的方法仍然是使用命名参数。 - Tom

10

无需创建重载方法,只需使用下面所示的带参数的单个方法即可

// Call params method with one to four integer constant parameters.
//
int sum0 = addTwoEach();
int sum1 = addTwoEach(1);
int sum2 = addTwoEach(1, 2);
int sum3 = addTwoEach(3, 3, 3);
int sum4 = addTwoEach(2, 2, 2, 2);

感谢您的输入,但我认为这种重载根本不是解决方案,因为使用params或不使用它,我们只需传递一个集合类型即可覆盖任何数量的集合。 - MasterMastic
你说的有一定道理,但更酷的是没有输入参数的重载,例如 int sum1 = addTwoEach(); - electricalbah

5

params 还允许您使用单个参数调用该方法。

private static int Foo(params int[] args) {
    int retVal = 0;
    Array.ForEach(args, (i) => retVal += i);
    return retVal;
}

例如,使用Foo(1);代替Foo(new int[] { 1 });。在需要传递单个值而不是整个数组的情况下,这种方式非常有用。方法仍然以同样的方式处理,但是这种调用方式会使代码更简洁。


5

添加“params”关键字本身就表明了在调用该方法时可以传递多个参数,而不使用它是不可能的。更具体地说:

static public int addTwoEach(params int[] args)
{
    int sum = 0;

    foreach (var item in args)
    {
        sum += item + 2;
    }

    return sum;
}

当您调用上述方法时,可以通过以下任何一种方式进行调用:
  1. addTwoEach()
  2. addTwoEach(1)
  3. addTwoEach(new int[]{ 1, 2, 3, 4 })
但是,当您删除params关键字时,以上给出的三种方式中只有第三种方式能正常工作。对于第一种和第二种情况,您将会得到一个错误。

1

还有一件重要的事情需要强调。最好使用params,因为它对性能更好。当您调用一个带有params参数的方法,并且没有传递任何内容时:

public void ExampleMethod(params string[] args)
{
// do some stuff
}

调用:

ExampleMethod();

从 .Net Framework 4.6 开始,新版本的 .Net Framework 做了以下改变:
ExampleMethod(Array.Empty<string>());

这个 Array.Empty 对象可以被框架后续重复使用,因此不需要进行冗余的分配。当您像这样调用此方法时,这些分配将会发生:
 ExampleMethod(new string[] {});

0

它增强了代码的简洁性。既然可以简明扼要,为什么还要冗长呢?

using System;

namespace testingParams
{
    class Program
    {
        private void lengthy(int[] myArr)
        {
           foreach (var item in myArr)
           {
              //...
           }
        }
        private void concise(params int[] myArr) {
           foreach (var item in myArr)
           {
              //...
           }
        }

        static void Main(string[] args)
        {
            Program p = new Program();
            //Why be lengthy...:
            int[] myArr = new int[] { 1, 2, 3, 4, 5 };
            p.lengthy(myArr);
            //When you can be concise...:
            p.concise(1, 2, 3, 4, 5);
        }
    }
}

如果您删除关键字params,调用者代码将无法按预期工作。


0
可能听起来很蠢,但是 Params 不允许多维数组。而你可以将多维数组传递给一个函数。

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