【问题标题】:Why use the params keyword?为什么使用 params 关键字?
【发布时间】:2018-02-14 03:02:34
【问题描述】:

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

为什么要使用它?如果您编写了一个正在使用它的函数或方法,那么当您删除它时,代码仍然可以正常工作,100% 就像没有它一样。例如:

带参数:

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;
}

【问题讨论】:

  • 方法本身的代码仍然可以完美运行...调用代码很可能不会...
  • params 关键字表示可以传递或不传递给方法的可选参数。没有 params 关键字的数组意味着您必须将数组参数传递给方法。
  • Python 语言用星号 (*) 前缀参数很好地实现了相同的概念,正如提到的 here

标签: c# parameter-passing params variadic-functions variadic


【解决方案1】:

使用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;
}

【讨论】:

  • @Ken:您可能需要导入System.Linq 命名空间:)
  • 或者返回args.Sum(i => i + 2);
  • 但是,委托的总和确实增加了编译代码的复杂性,这可能会降低性能。在这种特殊情况下并不真正相关,因为它不会导致任何闭包,但值得了解编译器实际上在做什么以做出最佳选择。
  • 你也可以使用return args.Select(x => x + 2).Sum();
  • 当您添加 params 时,您锁定自己无法添加其他方法参数,而不会破坏您的调用者或方法解析。
【解决方案2】:

使用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 到无穷大时,可以使用数组。

【讨论】:

  • 实际上数组可以为空。 new int[0]。希望这可以帮助! :)
【解决方案3】:

它允许您在调用中添加任意数量的基本类型参​​数。

addTwoEach(10, 2, 4, 6)

而对于第二种形式,您必须使用数组作为参数

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

【讨论】:

    【解决方案4】:

    params 关键字的一个危险是,如果 对方法的调用已经编码,

    1. 有人意外/故意从方法签名中删除了一个/多个必需参数, 和
    2. 一个/多个必需在签名更改之前的params参数之前的参数与params参数的类型兼容,

    这些调用将继续编译一个/多个以前用于必需参数的表达式,这些参数被视为可选params参数。我刚刚遇到了最坏的情况:params 参数的类型为 object[]

    这是值得注意的,因为开发人员已经习惯了编译器在更常见的情况下用所有必需参数从方法中删除参数(因为预期的参数数量会改变)。

    对我来说,不值得走捷径。 (Type)[] 没有 params 将使用 0 到无穷大的参数 # 而不需要覆盖。最坏的情况是您必须在不适用的呼叫中添加, new (Type) [] {}

    顺便说一句,恕我直言,最安全(也是最易读的做法)是:

    1. 通过命名参数传递(我们现在可以在 C# 中做到 even ~2 decades,在 VB 中我们可以这样做;P),因为:

      1.1。这是唯一保证防止在对调用编码后参数顺序、兼容类型和/或计数更改后传递给参数的意外值的方式,

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

      1.3。它避免了计算逗号并从调用到签名来回跳转以查看正在为什么参数传递什么表达式,以及

      1.3.1。顺便说一句,仅此一个原因就应该是很多(就避免经常容易出错的 DRY 原则的违反而言,只是为了阅读代码更不用说 修改它),但如果有一个/多个本身包含逗号的表达式被传递,即多维数组引用或多参数函数调用,这个原因可能指数地更重要。在那种情况下,您甚至不能使用(即使可以,仍然会添加一个额外的步骤 per 参数 per 方法调用)在编辑器中的选择功能可自动为您计算逗号。

      1.4。如果您必须使用可选参数(params 或不),它允许您搜索通过特定可选参数的调用(因此,很可能不是或至少有可能不是默认值),

    (注意:原因 1.2. 和 1.3. 可以减轻和减少错误的机会,即使是在对初始调用进行编码时,更不用说何时必须读取和/或更改调用。)

    1. 这样做 ONE - PARAMETER - PER - LINE 以获得更好的可读性(因为:

      2.1。它不那么杂乱,而且

      2.2。它避免了必须向右和向左滚动(并且必须每行都这样做,因为大多数凡人无法阅读多行的左侧部分,向右滚动并阅读右侧部分)。

      2.3。它与我们已经演变成的赋值语句的“最佳实践”一致,因为传递的每个参数本质上都是一个赋值语句(将值或引用分配给局部变量)。就像那些在编码风格中遵循最新“最佳实践”的人不会梦想每行编写多个赋值语句,我们可能不应该(也不会曾经“最佳实践”赶上我的“天才”;P)在传递参数时这样做。

    注意事项

    1. 在以下情况下,传入名称与参数一致的变量无济于事:

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

      1.2。该方法比调用者明显更低级别/更通用,因此您不希望/能够将变量命名为与参数相同/相似(反之亦然),或者

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

    2. 拥有像 VS 这样的自动换行功能只会消除我上面给出的 8 个原因中的一个(#2.2)。在 VS 2015 之前,它没有自动缩进(!?!真的,MS?!?)这增加了原因 #2.2 的严重性。

    VS 应该有一个选项可以生成带有命名参数的方法调用 sn-ps(当然每行一个;P)和一个需要命名参数的编译器选项(在概念上类似于 Option Explicit VB,顺便说一句,prolly 的要求曾经被认为同样令人发指,但现在是 prolly 要求“最佳实践”)。事实上,“回到我的天”;),在 1991 年,我的职业生涯刚刚开始几个月,甚至在我使用(或什至见过)带有命名参数的语言之前,我就有了 anti-sheeple /“只是因为你可以,并不意味着你应该”/不要盲目地“切掉烤肉的末端”来模拟它(使用在线 cmets)而没有看到任何人这样做。当大多数这些语法开始时,不必使用命名参数(以及保存“'珍贵'”源代码击键的其他语法)是 Punch Card 时代的遗物。对于现代硬件和 IDE 以及更复杂的软件,可读性非常、非常、MUCH 更重要,这没有任何借口。 “阅读代码的频率远高于编写代码的频率”。只要您不复制非自动更新的代码,当有人(甚至您自己)稍后尝试阅读它时,每次节省的击键成本都可能成倍增加。

    【讨论】:

    • 我不明白。为什么你不能强制至少有一个?即使没有参数,也没有什么可以阻止您将 nullnew object[0] 作为参数传递。
    • 在可选参数之前拥有必需参数可能太危险了(以防在调用编码后删除一个或多个必需参数)。这可能就是为什么我在任何关于可选参数的文档中的示例代码中的可选参数之前从未见过必需参数的原因。顺便说一句,恕我直言,最安全(也是最易读的做法)是通过命名参数传递(我们现在可以在 C# 中做到 even 大约比在 VB 中要晚 2 年)。 VS 应该有一个选项可以生成带有命名参数的方法调用 sn-ps(并且每行有 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 参数的第一个元素传递,所有这些调用仍将无错误地编译。
    • 哦。好吧,好吧,在那种特殊情况下,这可能是不明智的。如果您需要一个字符串和 0 对多整数或其他任何内容,我认为没有任何理由避免它。
    【解决方案5】:

    无需创建重载方法,只需使用一个带参数的方法,如下所示

    // 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 或没有我们只会传递一个集合类型来涵盖任何数量的集合。
    • 你在某种程度上是对的,但让它很酷的是没有输入参数的重载,例如 int sum1 = addTwoEach();
    【解决方案6】:

    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 });。在您可能需要传入单个值而不是整个数组的情况下,这对于速记很有用。它仍然在方法中以相同的方式处理,但为这种方式调用提供了一些糖果。

    【讨论】:

      【解决方案7】:

      添加 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 关键字时,只有上述给定方式中的第三种方式可以正常工作。对于第一种和第二种情况,你会得到一个错误。

      【讨论】:

        【解决方案8】:

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

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

        呼叫:

        ExampleMethod();
        

        然后新版本的 .Net Framework 执行此操作(来自 .Net Framework 4.6):

        ExampleMethod(Array.Empty<string>());
        

        这个Array.Empty对象以后可以被框架重用,所以不需要做多余的分配。当您像这样调用此方法时,将发生这些分配:

         ExampleMethod(new string[] {});
        

        【讨论】:

          【解决方案9】:

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

          【讨论】:

            【解决方案10】:

            另一个例子

            public IEnumerable<string> Tokenize(params string[] words)
            {
              ...
            }
            
            var items = Tokenize(product.Name, product.FullName, product.Xyz)
            

            【讨论】:

              【解决方案11】:

              它增强了简洁。既然可以快速,为什么还要传统?

              using System;
              
              namespace testingParams
              {
                  class Program
                  {
                      private void canOnlyBeCalledSlowly(int[] myArr)
                      {
                          Console.WriteLine("Snore");
                      }
                      private void canBeCalledQuickly(params int[] myArr) {
                          Console.WriteLine("That was quick");
                      }
              
                      static void Main(string[] args)
                      {
                          Program p = new Program();
                          //We're being conventional here:
                          int[] myArr = new int[] { 1, 2, 3, 4, 5 };
                          p.canOnlyBeCalledSlowly(myArr);
                          //We're being quick here:
                          p.canBeCalledQuickly(1, 2, 3);
                      }
                  }
              }
              

              【讨论】: