Forcing named arguments in C#

青春壹個敷衍的年華 提交于 2020-03-17 06:31:29

问题


C# 4 introduced a feature called named arguments which is especially useful in scenarios like

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

Is there a way to force using named arguments? Maybe some attribute to apply to a method or a compiler switch I'm not aware of? I guess it can be done with code inspector tools but just want to know if there is other way.

p.s.

For those interested why one may need it and why not just use a class/struct to utilize object initializers there are scenarios when it's impossible. Like calls to libraries not in your control or weird code conventions you have to obey.


回答1:


No, not in the C# language. It will always accept positional parameters if all the parameters are supplied.

You could build a custom FxCop rule or an StyleCop rule to enforce this - as pointed out in the comments, it is likely a StyleCop rule you would be interested in (thanks to Kris).




回答2:


It's possible to force the callers to always use named args. I wouldn't do this in most circumstances because it's rather ugly, but it depends on how badly safe method usage is needed.

Here is the solution:

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

The first parameter is a dummy that shouldn't be used (and is compiled away in Release for efficiency). However, it ensures that all following parameters have to be named.

Valid usage is any combination of the named parameters:

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

When attempting to use positional parameters, the code won't compile.




回答3:


I've also sought a way to force named arguments. Optional parameters can be dangerous, especially if you have multiple parameters of the same type. Overloads are almost always a safer solution, but there are times when you have a method that can take many combination of arguments, so creating 20 overloads to account for ever possibility is overkill.

In extreme situations where it is of the utmost importance that arguments be named at all times, I will create an argument class with no defined constructor. In your case, you could do this:

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

Call it like this:

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

You could also simplify it like this:

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

int RegisterUser(string nameFirst, string nameLast, UserRegistrationArguments args = null)

...and do this:

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

This way, you only have one optional parameter and that's for your optional parameters.

Edit: Maybe I didn't read the OP correctly. You're not using optional arguments? If not then this answer probably doesn't help you.




回答4:


Sorry for a shameless plug!
I implemented a Roslyn analyzer to enforce using named arguments for a method.

So if you install the RequireNamedArgs analyzer and add a special comment before a method that should be invoked with named arguments:

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

The analyzer will emit an error if a caller attempts to use positional arguments instead of named.

Take a look at it in action:

If you decide to give it a go -- do so at your own risk :)




回答5:


I'm using another method. In my setup I have 1 parameter which I always expect, then come a bunch of optional strings which I really want to be sure the user chose actively. So my first string in this list is a "trap" value, which if set, throws an error. Like this:

    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.");

    }

Hope you like it :)



来源:https://stackoverflow.com/questions/11300645/forcing-named-arguments-in-c-sharp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!