【问题标题】:Practical Reasons to use C# "named arguments"使用 C#“命名参数”的实际原因
【发布时间】:2015-09-06 11:39:04
【问题描述】:

我发现 C# 中的“命名参数”这一特性非常奇怪,因为我从中发现了两个缺陷。书中说“命名参数使您能够“以任何顺序传递参数”。

我认为这个 C# 功能存在两个缺陷:

  1. 它违反了计算机科学中的“信息隐藏”。 (即:使用该方法的最终用户需要知道参数名称和数据类型才能使用该功能。)来自 Java 背景,这很奇怪。为什么要将参数名称暴露给用户末尾?

  2. 它容易产生歧义,从而导致错误。 (程序员需要做额外的思考,当程序员最终编写使用相同方法名称的方法(也称为重载方法)时,问题可能会蔓延。当您有两个具有相同名称的方法时,您总是会得到“调用不明确即使另一个方法具有不同数据类型的额外参数,彼此也具有相同的参数。我能想到的唯一解决方法是使数据类型成为“强制参数”,也就是没有默认值的参数,所以编译器会这样做不要混淆。但是这个修复只是一个绷带解决方案,会导致另一个最坏的情况(见下文)

这个行业的人至今还在使用这个概念吗?如果是这样,为什么要打破这两个规则以赋予“在调用方法时以任意顺序传递参数的能力”?

TLDR:一个示例,通过介绍可能的最坏情况(编译器选择了错误的方法调用......尽管这两种方法相似),从而使我所谈论的内容更加清晰:

namespace ConsoleApplication1
{
    class Venusaur
    {
        static void Main(string[] args)
        {

            new Venusaur().optMethod(fourth: "s");
        }

        public void optMethod(  string third , string fourth =  "hello", int fifth = 23, string two = "w")
        {
            // what if I wanted this method to run instead of the method below me
            Console.WriteLine("did not execute");
        }


        public void optMethod(string third = "Byte", string fourth = "hello",  int fifth = 4)
        {
            // But this method ran instead
            Console.WriteLine("run");

        }


    }
}

【问题讨论】:

  • 如果你想运行optMethod的第一个声明,你需要明确指定third变量:new Venusaur().optMethod("thirdStr", fourth: "s");
  • 我不确定这个问题是否会被考虑。但是,要回答您的问题:使用命名参数是非常不寻常的,即使它在 C# 中是允许的。我能回忆起的唯一真实世界的例子是 XNA 和 MonoGame(老实说,它看起来很丑很奇怪,我认为他们本可以做得更好)。
  • @jsve 显式指定第三个变量将不起作用,因为这两种方法的参数名称相同,因此编译器会感到困惑。它会产生“歧义方法调用错误”。我已经尝试过这段代码: new Venusaur().optMethod(third: "thirdStr", Fourth: "s");不太清楚你是如何“突出显示你的代码”来响应和回复的。
  • @Nicholas 我明白你的意思了。 C# 的方法解析很复杂,但它被很好地指定并且命名参数也不会改变这些规则:stackoverflow.com/questions/5173339/…

标签: c# methods parameters arguments optional-parameters


【解决方案1】:

命名参数是另一项技术,被添加以使与 COM(和类似技术)的互操作性更容易。

method to open a Word document using COM

Document Open(
    [In] ref object FileName, 
    [In, Optional] ref object ConfirmConversions, 
    [In, Optional] ref object ReadOnly, 
    [In, Optional] ref object AddToRecentFiles, 
    [In, Optional] ref object PasswordDocument, 
    [In, Optional] ref object PasswordTemplate, 
    [In, Optional] ref object Revert, 
    [In, Optional] ref object WritePasswordDocument, 
    [In, Optional] ref object WritePasswordTemplate, 
    [In, Optional] ref object Format, 
    [In, Optional] ref object Encoding, 
    [In, Optional] ref object Visible, 
    [In, Optional] ref object OpenAndRepair, 
    [In, Optional] ref object DocumentDirection, 
    [In, Optional] ref object NoEncodingDialog, 
    [In, Optional] ref object XMLTransform
);

它有不少于16个参数,其中15个是可选的。

如果您想指定XMLTransform 参数但不关心其余部分怎么办?

您必须使用位置参数指定其余部分。使用命名参数,调用简单地变成:

doc.Open("somefilename.doc", XMLTransform: xxx);

除了dynamic 和其他一些东西,它并不是解决很多问题的好方法,但是当你有一个 API 严重依赖具有大量参数的方法时,其中大部分是可选的,命名参数是有意义的。

是的,您可以乱序指定参数。这并不意味着它是一个好主意或它解决了某人真正遇到的问题。

【讨论】:

    【解决方案2】:

    我发现你的第一个论点完全似是而非。无论喜欢与否,参数的名称每个方法语义的一部分,特别是对于将在子类中覆盖的抽象类和方法。参数的名称是向方法用户发出关于该方法语义以及每个参数在该语义中的角色的关键信号。此外,为了理解整个类层次结构中的语义,参数名称必须保持一致,并且不受每个级别的单个程序员的突发奇想影响。

    我觉得你的第二个论点完全无法理解。如果您在这里确实有一个有效的观点,我建议您重写它以提高清晰度。

    也就是说,我很少在方法调用中使用命名参数;接受两个以上相同类型的连续参数的方法除外。在这种情况下,我喜欢给它们命名,以便作为代码的未来读者,我实际上可以破译该方法的使用,而不必经常悬停以查看智能感知。

    【讨论】:

    • 参数名称不是方法签名的一部分。根据 C# 规范 (3.6):方法的签名由方法的名称、类型参数的数量以及每个形式参数的类型和种类(值、引用或输出)组成,按从左到右的顺序考虑。
    • 虽然重载解析会使用参数名来寻找符合条件的方法。
    • @TheodorosChatzigiannakis:我已将 signature 更改为 semantics,因为这符合您提出的技术要点,并且更清楚地说明了我的论点的意图。然而,正如 Lasse Karlsen 所指出的,参数名称也没有完全排除编译器对代码的解释。
    • @PieterGeerkens 很公平。我要么重新考虑我所说的第二部分,要么接受 C# 的怪癖。只是Java没有这个叫做“命名参数”的特性,使用它还是陌生的。
    【解决方案3】:

    1) 我不明白“信息隐藏”在哪里;方法签名是您要求最终用户提供给您的内容。他显然需要知道数据类型和名称来消除参数为什么指的是什么的歧义。此外,他仍然不知道您将如何处理您要求的那些参数(可能什么都没有),所以需要对他隐藏的内容仍然是隐藏的。

    2)这里的问题是对于仅在参数数量上有所不同的两个重载的错误选择,再加上省略某些参数的能力(由可选参数提供)使您失去区分它们的“好处”(除非你给出所有论点)

    命名参数(部分)引入了用于互操作场景的动态;例如使用 Office 自动化,在 C#3 中您需要添加一堆 Type.Missing 参数,然后在 C#4 中您可以提供您想要/需要的参数。

    【讨论】:

      【解决方案4】:

      关于 COM 互操作的观点是最重要的。

      如果我想要使用的方法签名中有一个布尔参数但没有明显的变量可以使用,我个人倾向于使用它。 示例:

      我觉得更易读

      var isSuccessful = myObject.Method(isRecursive: true)
      

      然后

      var isSuccessful = myObject.Method(true);
      

      最终你可以使用更详细的语法

      var isRecursive = true;
      var isSuccessful = myObject.Method(isRecursive);
      

      避免命名参数。

      这样调用方法的明显缺点是,如果由于某种原因方法的签名发生更改,您的代码将不再构建。

      【讨论】:

        猜你喜欢
        • 2012-04-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-19
        • 1970-01-01
        • 2013-01-15
        相关资源
        最近更新 更多