【问题标题】:Forcing named arguments in C#在 C# 中强制命名参数
【发布时间】:2012-07-03 06:41:37
【问题描述】:

C# 4 引入了一个名为 named arguments 的功能,该功能在以下场景中特别有用

int RegisterUser(string nameFirst, string nameLast, string nameMiddle, string email)

有没有办法强制使用命名参数?也许某些属性适用于我不知道的方法或编译器开关?我想这可以使用代码检查器工具来完成,但只是想知道是否有其他方法。

附言

对于那些对为什么需要它以及为什么不使用类/结构来利用object initializers 感兴趣的人,在某些情况下这是不可能的。比如调用不受你控制的库或你必须遵守的奇怪的代码约定。

【问题讨论】:

  • 除非您的 args 大多是可选的,否则我认为命名 args 没有多大用处。
  • @BobKaufman 我猜相反。当您编译代码时,它会“忘记”它是否被称为位置参数、命名或默认值。
  • @driis,当方法签名包含许多相同类型的参数时,调用时很容易出错。
  • 所以这是您要强制执行的约定。那么我认为你应该遵循 FxCop 规则。
  • @BrunoBrant 命名参数使代码更加自我记录,因此更易于维护。例如,有多少 API 必须为行和列传入整数?我敢打赌,10 次中有 9 次,人们必须去检查方法签名以确保参数在正确的位置(编写和阅读代码的人)。命名参数使整个练习变得无关紧要。

标签: c# .net c#-4.0 named-parameters


【解决方案1】:

可以强制调用者始终使用命名参数。在大多数情况下我不会这样做,因为它相当丑陋,但这取决于需要多么安全的方法使用。

解决办法如下:

    int RegisterUser(
#if DEBUG
      int _ = 0,
#endif
      string nameFirst = null,
      string nameLast = null,
      string nameMiddle = null,
      string email = null) { /*...*/ }

第一个参数是一个不应该使用的虚拟参数(为了提高效率,它在 Release 中被编译掉了)。但是,它确保必须命名所有以下参数。

有效用法是命名参数的任意组合:

    RegisterUser();
    RegisterUser(nameFirst: "Joe");
    RegisterUser(nameFirst: "Joe", nameLast: "Smith");
    RegisterUser(email: "joe.smith@example.com");

当尝试使用位置参数时,代码不会编译。

【讨论】:

  • 使用您的编译器预处理器指令,如果未定义 DEBUG 符号,它就好像 int _ = 0 甚至不存在,因此它对发布版本没有任何影响。 . 我不认为这个答案会像你认为的那样......
  • 效果是未使用的带默认值的“_”参数强制后面的其余参数都具有默认值,并在调用时命名。
  • 丑陋但聪明。
  • 虚拟参数也出现在 intellisense/R# 中,真可惜!
【解决方案2】:

不,不是 C# 语言。如果提供了所有参数,它将始终接受位置参数。

您可以通过 build a custom FxCop ruleStyleCop rule 强制执行此操作 - 正如 cmets 中所指出的,您可能会对 StyleCop 规则感兴趣(感谢 Kris)。

【讨论】:

  • 您无法创建自定义 FxCop 规则,因为 FxCop 规则适用于二进制文件,而不是源代码,并且命名参数被“编译掉”。我想你可以制定一个自定义 StyleCop 规则。
  • @KrisVandermotten,我认为你是对的,但是 FxCop 可以从 PDB 中提取信息,但我不知道命名参数的使用是否进入 PDB。如果不是这样,我想需要一个风格警察规则。
  • 不,至于命名参数是 PDB:“计算机说不!”。
  • 对不起,我实现了一个roslyn analyzer 来强制使用方法的命名参数。如果您决定试一试——请自行承担风险:)
【解决方案3】:

对不起一个无耻的插件!
我实现了一个Roslyn analyzer 来强制对方法使用命名参数。

因此,如果您 install the RequireNamedArgs analyzer 并在应使用命名参数调用的方法之前添加特殊注释:

//[RequireNamedArgs]
int RegisterUser(string nameFirst, string nameLast, string nameMiddle, string email)

如果调用者尝试使用位置参数而不是命名参数,分析器将发出错误。

看看它的实际效果:

如果您决定试一试——风险自负:)

【讨论】:

    【解决方案4】:

    我还寻求一种强制命名参数的方法。可选参数可能很危险,尤其是当您有多个相同类型的参数时。重载几乎总是一种更安全的解决方案,但有时您的方法可以采用多种参数组合,因此创建 20 个重载来考虑各种可能性是多余的。

    在极端情况下,始终命名参数至关重要,我将创建一个没有定义构造函数的参数类。在你的情况下,你可以这样做:

    public class UserRegistrationArguments
    {
        public string nameFirst { get; set; }
        public string nameLast { get; set; }
        public string nameMiddle { get; set; }
        public string email { get; set; }
    }
    

    这样称呼它:

    RegisterUser(new UserRegistrationArguments { nameFirst = "Bob", nameLast = "Slob" });
    

    你也可以这样简化:

    public class UserRegistrationArguments
    {
        public string nameMiddle { get; set; }
        public string email { get; set; }
    }
    
    int RegisterUser(string nameFirst, string nameLast, UserRegistrationArguments args = null)
    

    ...然后这样做:

    RegisterUser("Bob", "Slob", new UserRegistrationArguments { nameMiddle = "Teh" }); 
    

    这样,你只有一个可选参数,那就是你的可选参数。

    编辑:也许我没有正确阅读 OP。您没有使用可选参数吗?如果没有,那么这个答案可能对您没有帮助。

    【讨论】:

    • 同意。一个复杂的方法签名是一种代码味道,像这样的方法应该传递一个“上下文”实例。可能它不是唯一具有复杂参数的方法,您会发现同一类中的相邻方法也存在问题,这使得上下文对象非常实用。
    • 上下文对象的问题(我同意这通常是一个更好的解决方案!)是对象初始化器必须允许省略属性初始化;即所有参数实际上都是可选的。
    【解决方案5】:

    我正在使用另一种方法。在我的设置中,我有一个我一直期望的参数,然后是一堆可选的字符串,我真的想确保用户主动选择。所以我在这个列表中的第一个字符串是一个“陷阱”值,如果设置它会引发错误。像这样:

        public HtmlString Toolbar(DynamicEntity target = null, string dontRelyOnParameterOrder = Constants.RandomProtectionParameter, string actions = null, string contentType = null, object prefill = null)
        {
            if (!Enabled) return null;
            protectAgainstMissingParameterNames(dontRelyOnParameterOrder);
    
            var toolbar = new ItemToolbar(target, actions, contentType, prefill);
    
            return new HtmlString(toolbar.Toolbar);
        }
    
        private void protectAgainstMissingParameterNames(string criticalParameter)
        {
            if(criticalParameter != Constants.RandomProtectionParameter)
                throw new Exception("when using the toolbar command, please use named parameters - otherwise you are relying on the parameter order staying the same.");
    
        }
    

    希望你喜欢 :)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-12-22
      • 2020-08-28
      • 2013-09-30
      • 2012-09-03
      • 1970-01-01
      • 2018-10-17
      • 2023-03-20
      • 2011-06-16
      相关资源
      最近更新 更多