98

I was watching Anders' talk about C# 4.0 and sneak preview of C# 5.0, and it got me thinking about when optional parameters are available in C# what is going to be the recommended way to declare methods that do not need all parameters specified?

For example something like the FileStream class has about fifteen different constructors which can be divided into logical 'families' e.g. the ones below from a string, the ones from an IntPtr and the ones from a SafeFileHandle.

FileStream(string,FileMode);
FileStream(string,FileMode,FileAccess);
FileStream(string,FileMode,FileAccess,FileShare);
FileStream(string,FileMode,FileAccess,FileShare,int);
FileStream(string,FileMode,FileAccess,FileShare,int,bool);

It seems to me that this type of pattern could be simplified by having three constructors instead, and using optional parameters for the ones that can be defaulted, which would make the different families of constructors more distinct [note: I know this change will not be made in the BCL, I'm talking hypothetically for this type of situation].

What do you think? From C# 4.0 will it make more sense to make closely related groups of constructors and methods a single method with optional parameters, or is there a good reason to stick with the traditional many-overload mechanism?

Greg Beech
  • 133,383
  • 43
  • 204
  • 250

13 Answers13

130

I'd consider the following:

  • Do you need your code to be used from languages which don't support optional parameters? If so, consider including the overloads.
  • Do you have any members on your team who violently oppose optional parameters? (Sometimes it's easier to live with a decision you don't like than to argue the case.)
  • Are you confident that your defaults won't change between builds of your code, or if they might, will your callers be okay with that?

I haven't checked how the defaults are going to work, but I'd assume that the default values will be baked into the calling code, much the same as references to const fields. That's usually okay - changes to a default value are pretty significant anyway - but those are the things to consider.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 26
    +1 for the wisdom on pragmatism: _Sometimes it's easier to live with a decision you don't like than to argue the case._ – legends2k Jul 30 '14 at 09:31
  • 16
    @romkyns: No, the effect of overloads isn't the same thing with point 3. With overloads providing the defaults, the defaults are *within the library code* - so if you change the default and provide a new version of the library, callers will see the new default without recompilation. Whereas with optional parameters, you'd need to recompile to "see" the new defaults. A lot of the time it's not an important distinction, but it *is* a distinction. – Jon Skeet Feb 07 '15 at 15:03
  • 1
    hi @JonSkeet, i would like to know if we use both i.e function with optional paramater and other with overloading which method will be called?? eg Add(int a, int b) and Add(int a,int b,int c=0) and function call say: Add(5,10); which method will be called overloaded function or optional parameter function ? thanks :) – SHEKHAR SHETE Sep 22 '16 at 05:05
  • @Shekshar: Have you tried it? Read the spec for details, but basically in a tie-breaker a method where the compiler hasn't had to fill in any optional parameters wins. – Jon Skeet Sep 22 '16 at 05:07
  • 1
    @JonSkeet just now i tried with above...the function overloading wins over optional parameter :) – SHEKHAR SHETE Sep 22 '16 at 05:13
19

When a method overload normally performs the same thing with a different number of arguments then defaults will be used.

When a method overload performs a function differently based on its parameters then overloading will continue to be used.

I used optional back in my VB6 days and have since missed it, it will reduce a lot of XML comment duplication in C#.

cfeduke
  • 23,100
  • 10
  • 61
  • 65
12

I've been using Delphi with optional parameters forever. I've switched to using overloads instead.

Because when you go to create more overloads, you'll invariably conflict with an optional parameter form, and then you'll have to convert them to non-optional anyway.

And I like the notion that there's generally one super method, and the rest are simpler wrappers around that one.

CarenRose
  • 1,266
  • 1
  • 12
  • 24
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • 2
    I very much agree with this, however there is the caveat that when you have a method that takes multiple (3+) parameters, that are by nature all "optional" (could be substituted with a default) you could end up with many permutations of the method signature for no more benefit. Consider `Foo(A, B, C)` requires `Foo(A)`, `Foo(B)`, `Foo(C)`, `Foo(A, B)`, `Foo(A, C)`, `Foo(B, C)`. – Dan Lugg May 07 '17 at 23:46
7

I will definitely be using the optional parameters feature of 4.0. It gets rid of the ridiculous ...

public void M1( string foo, string bar )
{
   // do that thang
}

public void M1( string foo )
{
  M1( foo, "bar default" ); // I have always hated this line of code specifically
}

... and puts the values right where the caller can see them ...

public void M1( string foo, string bar = "bar default" )
{
   // do that thang
}

Much more simple and much less error prone. I've actually seen this as a bug in the overload case ...

public void M1( string foo )
{
   M2( foo, "bar default" );  // oops!  I meant M1!
}

I have not played with the 4.0 complier yet, but I would not be shocked to learn that the complier simply emits the overloads for you.

JP Alioto
  • 44,864
  • 6
  • 88
  • 112
6

Optional parameters are essentially a piece of metadata which directs a compiler that's processing a method call to insert appropriate defaults at the call site. By contrast, overloads provide a means by which a compiler can select one of a number of methods, some of which might supply default values themselves. Note that if one tries to call a method that specifies optional parameters from code written in a language which doesn't support them, the compiler will require that the "optional" parameters be specified, but since calling a method without specifying an optional parameter is equivalent to calling it with a parameter equal to the default value, there's no obstacle to such languages calling such methods.

A significant consequence of binding of optional parameters at the call site is that they will be assigned values based upon the version of the target code which is available to the compiler. If an assembly Foo has a method Boo(int) with a default value of 5, and assembly Bar contains a call to Foo.Boo(), the compiler will process that as a Foo.Boo(5). If the default value is changed to 6 and assembly Foo is recompiled, Bar will continue to call Foo.Boo(5) unless or until it is recompiled with that new version of Foo. One should thus avoid using optional parameters for things that might change.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • Re: _"One should thus avoid using optional parameters for things that might change."_ I agree that this can be problematic if the change goes unnoticed by client code. However, the same problem exists when the default value is hidden inside a method overload: `void Foo(int value) … void Foo() { Foo(42); }`. From the outside, the caller does not know what default value gets used, nor when it might change; one would have to monitor written documentation for that. Default values for optional parameters can be seen as just that: documentation-in-code what the default value is. – stakx - no longer contributing Mar 01 '17 at 12:39
  • @stakx: If a parameterless overload chains to an overload with a parameter, changing the "default" value of that parameter and recompiling the definition of the overload will change the value it uses *even if the calling code is not recompiled*. – supercat Mar 01 '17 at 22:21
  • True but that doesn't make it more problematic than the alternative. In one case (method overload), calling code has no say in the default value. This can be appropriate if the calling code really doesn't care at all about the optional parameter and what it means. In the other case (optional parameter with default value), previously compiled calling code won't be affected when the default value changes. This can also be appropriate when the calling code actually cares about the parameter; omitting it in source is like saying, "the currently suggested default value is OK for me." – stakx - no longer contributing Mar 02 '17 at 08:52
  • The point I'm trying to make here is that while there are consequences to either approach (like you pointed out), they are not inherently advantageous or disadvantageous. That depends also on the calling code's needs and goals. From that POV, I found the verdict in your answer's last sentence somewhat too rigid. – stakx - no longer contributing Mar 02 '17 at 08:56
  • @stakx: I said "avoid using" rather than "never use". If changing X will mean that the next recompilation of Y will change Y's behavior, that will either require that a build system be configured so that every recompilation of X also recompiles Y (slowing things down), or create the risk that a programmer will change X in a way that will break Y the next time it's compiled, and only discover such breakage at a later time when Y is changed for some totally unrelated reason. Default parameters should only be used when their advantages outweigh such costs. – supercat Mar 02 '17 at 16:12
5

It can be argued whether optional arguments or overloads should be used or not, but most importantly, each have their own area where they are irreplaceable.

Optional arguments, when used in combination with named arguments, are extremely useful when combined with some long-argument-lists-with-all-optionals of COM calls.

Overloads are extremely useful when method is able to operate on many different argument types (just one of examples), and does castings internally, for instance; you just feed it with any data type that makes sense (that is accepted by some existing overload). Can't beat that with optional arguments.

mr.b
  • 4,932
  • 11
  • 38
  • 55
3

In many cases optional parameters are used to switch execution. For example:

decimal GetPrice(string productName, decimal discountPercentage = 0)
{

    decimal basePrice = CalculateBasePrice(productName);

    if (discountPercentage > 0)
        return basePrice * (1 - discountPercentage / 100);
    else
        return basePrice;
}

Discount parameter here is used to feed the if-then-else statement. There is the polymorphism that wasn't recognized, and then it was implemented as an if-then-else statement. In such cases, it is much better to split the two control flows into two independent methods:

decimal GetPrice(string productName)
{
    decimal basePrice = CalculateBasePrice(productName);
    return basePrice;
}

decimal GetPrice(string productName, decimal discountPercentage)
{

    if (discountPercentage <= 0)
        throw new ArgumentException();

    decimal basePrice = GetPrice(productName);

    decimal discountedPrice = basePrice * (1 - discountPercentage / 100);

    return discountedPrice;

}

In this way, we have even protected the class from receiving a call with zero discount. That call would mean that the caller thinks that there is the discount, but in fact there is no discount at all. Such misunderstanding can easily cause a bug.

In cases like this, I prefer not to have optional parameters, but to force the caller explicitly select the execution scenario that suits its current situation.

The situation is very similar to having parameters that can be null. That is equally bad idea when implementation boils to statements like if (x == null).

You can find detailed analysis on these links: Avoiding Optional Parameters and Avoiding Null Parameters

Zoran Horvat
  • 10,924
  • 3
  • 31
  • 43
3

One of my favourites aspects of optional parameters is that you see what happens to your parameters if you do not provide them, even without going to the method definition. Visual Studio will simply show you the default value for the parameter when you type the method name. With an overload method you are stuck with either reading the documentation (if even available) or with directly navigating to the method's definition (if available) and to the method that the overload wraps.

In particular: the documentation effort may increase rapidly with the amount of overloads, and you will probably end up copying already existing comments from the existing overloads. This is quite annoying, as it does not produce any value and breaks the DRY-principle). On the other hand, with an optional parameter there's exactly one place where all the parameters are documented and you see their meaning as well as their default values while typing.

Last but not least, if you are the consumer of an API you may not even have the option of inspecting the implementation details (if you don't have the source code) and therefore have no chance to see to which super method the overloaded ones are wrapping. Thus you're stuck with reading the doc and hoping that all default values are listed there, but this is not always the case.

Of course, this is not an answer that handles all aspects, but I think it adds one which has not be covered so far.

mklement0
  • 382,024
  • 64
  • 607
  • 775
MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111
3

One caveat of optional parameters is versioning, where a refactor has unintended consequences. An example:

Initial code

public string HandleError(string message, bool silent=true, bool isCritical=true)
{
  ...
}

Assume this is one of many callers of the above method:

HandleError("Disk is full", false);

Here the event is not silent and is treated as critical.

Now let's say after a refactor we find that all errors prompt the user anyway, so we no longer need the silent flag. So we remove it.

After refactor

The former call still compiles, and let's say it slips through the refactor unchanged:

public string HandleError(string message, /*bool silent=true,*/ bool isCritical=true)
{
  ...
}

...

// Some other distant code file:
HandleError("Disk is full", false);

Now false will have an unintended effect, the event will no longer be treated as critical.

This could result in a subtle defect, since there will be no compile or runtime error (unlike some other caveats of optionals, such as this or this).

Note that there are many forms of this same problem. One other form is outlined here.

Note also that strictly using named parameters when calling the method will avoid the issue, such as like this: HandleError("Disk is full", silent:false). However, it may not be practical to assume that all other developers (or users of a public API) will do so.

For these reasons I would avoid using optional parameters in a public API (or even a public method if it might be used widely) unless there are other compelling considerations.

Zach
  • 63
  • 5
  • "Now false will have an unintended effect, the event will no longer be treated as critical."--I am not sure if this is correct. It would never have been treated as critical before the refactor also because in `HandleError("Disk is full", false);` the `false` is for `silent`. – CodingYoshi Apr 12 '22 at 17:30
2

I'm looking forward to optional parameters because it keeps what the defaults are closer to the method. So instead of dozens of lines for the overloads that just call the "expanded" method, you just define the method once and you can see what the optional parameters default to in the method signature. I'd rather look at:

public Rectangle (Point start = Point.Zero, int width, int height)
{
    Start = start;
    Width = width;
    Height = height;
}

Instead of this:

public Rectangle (Point start, int width, int height)
{
    Start = start;
    Width = width;
    Height = height;
}

public Rectangle (int width, int height) :
    this (Point.Zero, width, height)
{
}

Obviously this example is really simple but the case in the OP with 5 overloads, things can get crowded real quick.

Mark A. Nicolosi
  • 82,413
  • 11
  • 44
  • 46
  • 7
    I've heard optional parameters should be last, shouldn't they? – Ilya Ryzhenkov Oct 30 '08 at 22:04
  • Depends on your design. Perhaps the 'start' argument is normally important, except when it's not. Perhaps you have the same signature somewhere else, meaning something different. For a contrived example, public Rectangle(int width, int height, Point innerSquareStart, Point innerSquareEnd) {} – Robert P Oct 30 '08 at 22:23
  • 13
    From what they said in the talk, optional parameters must be after required parameters. – Greg Beech Oct 30 '08 at 22:57
2

While they are (supposedly?) two conceptually equivalent ways available for you to model your API from scratch, they unfortunately have some subtle difference when you need to consider runtime backward compatibility for your old clients in the wild. My colleague (thanks Brent!) pointed me to this wonderful post: Versioning issues with optional arguments. Some quote from it:

The reason that optional parameters were introduced to C# 4 in the first place was to support COM interop. That’s it. And now, we’re learning about the full implications of this fact. If you have a method with optional parameters, you can never add an overload with additional optional parameters out of fear of causing a compile-time breaking change. And you can never remove an existing overload, as this has always been a runtime breaking change. You pretty much need to treat it like an interface. Your only recourse in this case is to write a new method with a new name. So be aware of this if you plan to use optional arguments in your APIs.

RayLuo
  • 17,257
  • 6
  • 88
  • 73
1

To add a no-brainer when to use an overload instead of optionals:

Whenever you have a number of parameters that only make sense together, do not introduce optionals on them.

Or more generally, whenever your method signatures enable usage patterns which don't make sense, restrict the number of permutations of possible calls. E.g., by using overloads instead of optionals (this rule also holds true when you have several parameters of the same datatype, by the way; here, devices like factory methods or custom data types can help).

Example:

enum Match {
    Regex,
    Wildcard,
    ContainsString,
}

// Don't: This way, Enumerate() can be called in a way
//         which does not make sense:
IEnumerable<string> Enumerate(string searchPattern = null,
                              Match match = Match.Regex,
                              SearchOption searchOption = SearchOption.TopDirectoryOnly);

// Better: Provide only overloads which cannot be mis-used:
IEnumerable<string> Enumerate(SearchOption searchOption = SearchOption.TopDirectoryOnly);
IEnumerable<string> Enumerate(string searchPattern, Match match,
                              SearchOption searchOption = SearchOption.TopDirectoryOnly);
Sebastian Mach
  • 38,570
  • 8
  • 95
  • 130
0

Both Optional parameter , Method overload have there own advantage or disadvantage.it depends on your preference to choose between them.

Optional Parameter: available only in .Net 4.0. optional parameter reduce your code size. You can't define out and ref parameter

overloaded methods: You can Define Out and ref parameters. Code size will increase but overloaded method's are easy to understand.

sushil pandey
  • 752
  • 10
  • 9