Why use the params keyword?
C#Parameter PassingParamsVariadic FunctionsVariadicC# Problem Overview
I know this is a basic question, but I couldn't find an answer.
Why use it? if you write a function or a method that's using it, when you remove it the code will still work perfectly, 100% as without it. E.g:
With params:
static public int addTwoEach(params int[] args)
{
int sum = 0;
foreach (var item in args)
sum += item + 2;
return sum;
}
Without params:
static public int addTwoEach(int[] args)
{
int sum = 0;
foreach (var item in args)
sum += item + 2;
return sum;
}
C# Solutions
Solution 1 - C#
With params
you can call your method like this:
addTwoEach(1, 2, 3, 4, 5);
Without params
, you can’t.
Additionally, you can call the method with an array as a parameter in both cases:
addTwoEach(new int[] { 1, 2, 3, 4, 5 });
That is, params
allows you to use a shortcut when calling the method.
Unrelated, you can drastically shorten your method:
public static int addTwoEach(params int[] args)
{
return args.Sum() + 2 * args.Length;
}
Solution 2 - C#
Using params
allows you to call the function with no arguments. Without params
:
static public int addTwoEach(int[] args)
{
int sum = 0;
foreach (var item in args)
{
sum += item + 2;
}
return sum;
}
addtwoEach(); // throws an error
Compare with params
:
static public int addTwoEach(params int[] args)
{
int sum = 0;
foreach (var item in args)
{
sum += item + 2;
}
return sum;
}
addtwoEach(); // returns 0
Generally, you can use params when the number of arguments can vary from 0 to infinity, and use an array when numbers of arguments vary from 1 to infinity.
Solution 3 - C#
It allows you to add as many base type parameters in your call as you like.
addTwoEach(10, 2, 4, 6)
whereas with the second form you have to use an array as parameter
addTwoEach(new int[] {10,2,4,6})
Solution 4 - C#
One danger with params
Keyword is, if after Calls to the Method have been coded,
- someone accidentally / intentionally removes one/more required Parameters from the Method Signature, and
- one/more required Parameters immediately prior to the
params
Parameter prior to the Signature change were Type-Compatible with theparams
Parameter,
those Calls will continue to compile with one/more Expressions previously intended for required Parameters being treated as the optional params
Parameter. I just ran into the worst possible case of this: the params
Parameter was of Type object[]
.
This is noteworthy because developers are used to the compiler slapping their wrists with the much, much more common scenario where Parameters are removed from a Method with all required Parameters (because the # of Parameters expected would change).
For me, it's not worth the shortcut. (Type)[]
without params
will work with 0 to infinity # of Parameters without needing Overrides. Worst case is you'll have to add a , new (Type) [] {}
to Calls where it doesn't apply.
Btw, imho, the safest (and most readable practice) is to:
-
pass via Named Parameters (which we can now do even in C# ~2 decades after we could in VB ;P), because:
1.1. it's the only way that guarantees prevention of unintended values passed to Parameters after Parameter order, Compatible-Type and/or count change after Calls have been coded,
1.2. it reduces those chances after a Parameter meaning change, because the likely new identifier name reflecting the new meaning is right next to the value being passed to it,
1.3. it avoids having to count commas and jump back & forth from Call to Signature to see what Expression is being passed for what Parameter, and
1.3.1. By the way, this reason alone should be plenty (in terms of avoiding frequent error-prone violations of the DRY Principle just to read the code not to mention also modify it), but this reason can be exponentially more important if there are one/more Expressions being Passed that themselves contain commas, i.e. Multi-Dimensional Array Refs or Multi-Parameter Function Calls. In that case, you couldn't even use (which even if you could, would still be adding an extra step per Parameter per Method Call) a Find All Occurrences in a Selection feature in your editor to automate the comma-counting for you.
1.4. if you must use Optional Parameters (
params
or not), it allows you to search for Calls where a particular Optional Parameter is Passed (and therefore, most likely is not or at least has the possibility of being not the Default Value),
(NOTE: Reasons 1.2. and 1.3. can ease and reduce chances of error even on coding the initial Calls not to mention when Calls have to be read and/or changed.))
and
-
do so ONE - PARAMETER - PER - LINE for better readability (because:
2.1. it's less cluttered, and
2.2. it avoids having to scroll right & back left (and having to do so PER - LINE, since most mortals can't read the left part of multiple lines, scroll right and read the right part)).
2.3. it's consistent with the "Best Practice" we've already evolved into for Assignment Statements, because every Parameter Passed is in essence an Assignment Statement (assigning a Value or Reference to a Local Variable). Just like those who follow the latest "Best Practice" in Coding Style wouldn't dream of coding multiple Assignment Statements per line, we probably shouldn't (and won't once "Best Practice" catches up to my "genius" ;P ) do so when Passing Parameters.
NOTES:
-
Passing in Variables whose names mirror the Parameters' doesn't help when:
1.1. you're passing in Literal Constants (i.e. a simple 0/1, false/true or null that even "'Best Practices'" may not require you use a Named Constant for and their purpose can't be easily inferred from the Method name),
1.2. the Method is significantly lower-level / more generic than the Caller such that you would not want / be able to name your Variables the same/similar to the Parameters (or vice versa), or
1.3. you're re-ordering / replacing Parameters in the Signature that may result in prior Calls still Compiling because the Types happen to still be compatible.
-
Having an auto-wrap feature like VS does only eliminates ONE (#2.2) of the 8 reasons I gave above. Prior to as late as VS 2015, it did NOT auto-indent (!?! Really, MS?!?) which increases severity of reason #2.2.
VS should have an option that generates Method Call snippets with Named Parameters (one per line of course ;P) and a compiler option that requires Named Parameters (similar in concept to Option Explicit in VB which, btw, the requirement of was prolly once thought equally as outrageous but now is prolly required by "'Best Practices'"). In fact, "back in my day" ;), in 1991 just months into my career, even before I was using (or had even seen) a language with Named Parameters, I had the anti-sheeple / "just cuz you can, don't mean you should" / don't blindly "cut the ends of the roast" sense enough to simulate it (using in-line comments) without having seen anyone do so. Not having to use Named Parameters (as well other syntax that save "'precious'" source code keystrokes) is a relic of the Punch Card era when most of these syntaxes started. There's no excuse for that with modern hardware and IDE's and much more complex software where readability is much, Much, MUCH more important. "Code is read much more often than is written". As long as you're not duplicating non-auto-updated code, every keystroke saved is likely to cost exponentially more when someone (even yourself) is trying to read it later.
Solution 5 - C#
No need to create overload methods, just use one single method with params as shown below
// Call params method with one to four integer constant parameters.
//
int sum0 = addTwoEach();
int sum1 = addTwoEach(1);
int sum2 = addTwoEach(1, 2);
int sum3 = addTwoEach(3, 3, 3);
int sum4 = addTwoEach(2, 2, 2, 2);
Solution 6 - C#
params
also allows you to call the method with a single argument.
private static int Foo(params int[] args) {
int retVal = 0;
Array.ForEach(args, (i) => retVal += i);
return retVal;
}
i.e. Foo(1);
instead of Foo(new int[] { 1 });
. Can be useful for shorthand in scenarios where you might need to pass in a single value rather than an entire array. It still is handled the same way in the method, but gives some candy for calling this way.
Solution 7 - C#
Adding params keyword itself shows that you can pass multiple number of parameters while calling that method which is not possible without using it. To be more specific:
static public int addTwoEach(params int[] args)
{
int sum = 0;
foreach (var item in args)
{
sum += item + 2;
}
return sum;
}
When you will call above method you can call it by any of the following ways:
addTwoEach()
addTwoEach(1)
addTwoEach(new int[]{ 1, 2, 3, 4 })
But when you will remove params keyword only third way of the above given ways will work fine. For 1st and 2nd case you will get an error.
Solution 8 - C#
One more important thing needs to be highlighted. It's better to use params
because it is better for performance. When you call a method with params
argument and passed to it nothing:
public void ExampleMethod(params string[] args)
{
// do some stuff
}
call:
ExampleMethod();
Then a new versions of the .Net Framework do this (from .Net Framework 4.6):
ExampleMethod(Array.Empty<string>());
This Array.Empty
object can be reused by framework later, so there are no needs to do redundant allocations. These allocations will occur when you call this method like this:
ExampleMethod(new string[] {});
Solution 9 - C#
Might sound stupid, But Params doesn't allow multidimensional array. Whereas you can pass a multidimensional array to a function.
Solution 10 - C#
Another example
public IEnumerable<string> Tokenize(params string[] words)
{
...
}
var items = Tokenize(product.Name, product.FullName, product.Xyz)
Solution 11 - C#
It enhances brevity. Why be conventional when you can be quick?
using System;
namespace testingParams
{
class Program
{
private void canOnlyBeCalledSlowly(int[] myArr)
{
Console.WriteLine("Snore");
}
private void canBeCalledQuickly(params int[] myArr) {
Console.WriteLine("That was quick");
}
static void Main(string[] args)
{
Program p = new Program();
//We're being conventional here:
int[] myArr = new int[] { 1, 2, 3, 4, 5 };
p.canOnlyBeCalledSlowly(myArr);
//We're being quick here:
p.canBeCalledQuickly(1, 2, 3);
}
}
}