46

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.

UserControl
  • 14,766
  • 20
  • 100
  • 187
  • 4
    I don't see much use in named args unless your args are mostly optional. – Bruno Brant Jul 02 '12 at 20:27
  • 1
    @BobKaufman I would guess the opposite. When you compile the code, it "forgets" whether it was referred to as a positional parameter, named, or default value. – Tim S. Jul 02 '12 at 20:30
  • 1
    Why _do you_ need this ? Is it a code convention being imposed on you ? – driis Jul 02 '12 at 20:32
  • @TimS. You are absolutely right. Agreed, and withdrawn. – Bob Kaufman Jul 02 '12 at 20:34
  • 6
    @driis, when a method signature contains many arguments of the same type it's easy to make a mistake when calling it. – UserControl Jul 02 '12 at 20:35
  • 2
    So it is a convention you want to enforce. Then I think you should go for the FxCop rule. – driis Jul 02 '12 at 20:41
  • 17
    @BrunoBrant Named arguments make code much more self-documenting and therefore much more maintainable. For example, how many APIs are there where you must pass in integers for rows and columns? And I bet 9 times out of 10, people have to go and check the method signature to make sure the arguments are in the correct position (both those writing AND reading the code). Named arguments make that whole exercise irrelevant. – MgSam Jul 02 '12 at 21:32

5 Answers5

36

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.

user4698855
  • 2,877
  • 2
  • 16
  • 9
  • 2
    With your compiler preprocessor directive, it acts as if the `int _ = 0` doesn't even exist if the DEBUG symbol is not defined, so it doesn't have any effect whatsoever for a release build... I don't think this answer does what you think it does... – Zack Sep 16 '15 at 21:10
  • 4
    The effect is that the unused "_" parameter with default value forces the rest of following parameters to have default values, and be named when calling. – user4698855 Jan 08 '16 at 18:47
  • 20
    Ugly, but clever. – joelsand Oct 28 '16 at 19:51
  • the dummy param also shows up in intellisense/R#, which is a shame! – GoatInTheMachine Sep 05 '17 at 13:33
  • @Zack, I think his point was to cause a compiler error if the dummy arg is ever specified in a call and built in Release mode, thus reminding developers not to specify it. Although I would name it something like "doNotUseThisArg" or "forceNamedArgs". – Digiproc Jun 13 '23 at 21:26
28

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

driis
  • 161,458
  • 45
  • 265
  • 341
  • 7
    You cannot make a custom FxCop rule, because FxCop rules work on binaries, not source, and the named arguments are "compiled away". I suppose you could make a custom StyleCop rule though. – Kris Vandermotten Jul 02 '12 at 20:45
  • @KrisVandermotten, I think you are right, however FxCop can extract information from the PDB, but I don't know if the usage of named parameters goes into the PDB. If that is not the case, I guess a style cop rule is needed instead. – driis Jul 02 '12 at 20:52
  • 3
    Nope, as for named parameters is the PDB: "computer says no!". – Kris Vandermotten Jul 02 '12 at 20:53
  • 1
    Sorry for a plug, I implemented a [roslyn analyzer](https://github.com/mykolav/require-named-args-fs) to enforce using named arguments for a method. In case you decide to give it a try -- do so at your own risk :) – Myk Aug 17 '18 at 08:55
8

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 attribute to the 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.

You can implement RequiredNamedArgs as an empty attribute yourself, the analyzer looks for it by the type name only, so it doesn't matter which assembly or namespace the attribute is imported from.

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor)]
public class RequireNamedArgsAttribute : Attribute { }

Take a look at it in action: The analyzer in action

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

Myk
  • 1,520
  • 16
  • 19
4

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.

oscilatingcretin
  • 10,457
  • 39
  • 119
  • 206
  • Agreed. A complex method signature is a code smell, methods like this should be passed a "context" instance instead. Likely it's not the only method with the complex parameters, you'll see that neighboring methods in the same class also have the issue, making a context object very practical. – bboyle1234 May 01 '18 at 05:13
  • 1
    The problem with a context object (which I agree is generally a better solution!) is that necessarily object initializers allow omiting property initialization; i.e. all arguments are effectively optional. – Eamon Nerbonne Feb 04 '19 at 09:07
1

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 :)

iJungleBoy
  • 5,325
  • 1
  • 9
  • 21