0

There's a function I have in an API that a lot of other libraries use, but I want to adapt it for a new feature to take an added optional parameter. The method signature is:

protected Task EnvGetJsonAsync<TReceive>(string endpointName, params object[] pathArgs)

and I wanted to add "string key = null" after pathArgs, because I have 37 references to this function that are used to this format. So that I don't have to change all 37 references (Also most of the references don't need to the optional param) I just wanted to add an optional param at the end.

I tried changing "params object[]" to just "object[]" but it caused an error with all the references. So it looked like this

protected Task EnvGetJsonAsync<TReceive>(string endpointName, params object[] pathArgs, string id = null)

and then this

protected Task EnvGetJsonAsync<TReceive>(string endpointName, object[] pathArgs, string id = null)

but both caused issues. I want a way to allow the function to take on an optional param without breaking the other 37 references. Thank you!

slugster
  • 49,403
  • 14
  • 95
  • 145
Dy Y
  • 11
  • 4
  • 2
    You can't really do this. A params parameter must be the last parameter in the formal parameter list. However adding a parameter to method, even an optional one is a breaking change. Arguably it might be better to simply add a new method with a new name that takes the new parameter and then follows with an optional parameter list. – Mike Zboray Mar 26 '19 at 01:22
  • https://stackoverflow.com/questions/3948971/c-sharp-4-0-optional-parameters-and-params-do-not-work-together – G-Man Mar 26 '19 at 01:22
  • 2
    Instead of using optional parameter, can you use method overload and create method: `protected Task EnvGetJsonAsync(string endpointName, string id, param object[] pathArgs,)` instead. Internally it can both methods can call same private method and you would not break existing references. – Ankit Vijay Mar 26 '19 at 01:22
  • 1
    @AnkitVijay While this is legal, I will note that this can cause a subtle change in behavior, depending on what the id parameter does in the method. Imagine I had `EnvGetJsonAsync("endpointname", 'path/arg")`. pathArgs is an array of one item. When I recompile with the new library, overload resolution selects a different method and id is "path/arg" and pathArgs is empty. If the id parameter is semantically different, a new method is probably warranted. – Mike Zboray Mar 26 '19 at 01:27
  • Hi @mjwills and Mike I was writing faster than I was thinking. Thanks for correcting me :) – Ankit Vijay Mar 26 '19 at 01:38

3 Answers3

2

You cannot have optional parameters after params as per the documentation:

No additional parameters are permitted after the params keyword in a method declaration, and only one params keyword is permitted in a method declaration.

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/params

You can put optional parameters before your params, however because your params array type is an object you'll probably run into run time errors selecting the wrong method if your first pathArgs type is a string.

The best way would be to add a new method with your optional parameter, using a slightly different name. Your reference methods can then use the appropriate method if they have the key or not. You can then call the new method from the old with a null or default value for key:

protected Task EnvGetJsonAsync<TReceive>(string endpointName, params object[] pathArgs)
{
    return EnvGetJsonWithKeyAsync<TReceive>(endpointName, null, pathArgs);
}

protected Task EnvGetJsonWithKeyAsync<TReceive>(string endpointName, string key, params object[] pathArgs)
{
    // your method here
}
Steve
  • 9,335
  • 10
  • 49
  • 81
  • `The best way would be to add a new method with your optional parameter` Great solution, with no risk to existing callers. – mjwills Mar 26 '19 at 01:25
1

You can move your optional parameter before param. As per MSDN

No additional parameters are permitted after the params keyword in a method declaration, and only one params keyword is permitted in a method declaration.

It is better to create new method for new input and based on input you can call your existing method from there.

Overloading will not be working for param. You will not get any compile time error. It will call the last method at run time. You can check this fiddler for more.

Mohammad Arshad Alam
  • 9,694
  • 6
  • 38
  • 61
0

Just create a new method, move the implementation there and use the old one to call the new method. That way your 37 callers will never notice the change:

protected Task EnvGetJsonAsync<TReceive>(string endpointName, params object[] pathArgs)
{
  // this is the original method. Here we just call the new one
  return EnvGetJsonoAsync<TReceive>(endpointName, null, pathArgs);
}

protected Task EnvGetJsonAsync<TReceive>(string endpointName, string id, params object[] pathArgs) 
{
  .. the code implementation
}
jparaya
  • 1,331
  • 11
  • 15
  • 1
    `That way your 37 callers will never notice the change:` Unless they passed a string as their second parameter. ;) – mjwills Mar 26 '19 at 01:24
  • You're right, sir! In that case, the overload resolution will prefer the second method. Thanks! – jparaya Mar 26 '19 at 09:38