Core5: Overloading on optional parameters


[This post is part of a series, “wish-list for future versions of VB“]


IDEA: Overloading on optional parameters. Allow you to have overrides which differ only in their optional parameters. The rules for “overloaded method resolution” would have to be augmented with a new tie-breaker rule, e.g. like C# “… otherwise, if one overload had at least one missing optional argument, and another overload had no missing optional arguments, then prefer the second overload”.


SCENARIO: You wrote a library which initially looked like this:,
    Sub fopen(fn As String, Optional mode As OpenMode = OpenMode.Input)



But now you want to release a second version of your library which takes two parameters,


    Sub fopen(fn As String, Optional mode As OpenMode = OpenMode.Input,


              Optional password As String = “”)


You want to do this in such a way that you don’t break binary compatibility (i.e. applications that had been compiled against the old library should continue to run against the new one without recompiling). You also don’t want to break source compatibility (i.e. if an application compiled against the old library, it should recompile against the new library too).


This scenario requires “overloading on optional parameters” to work.


 


This scenario isn’t as clear-cut as you’d think. For a start, CLS guidelines say to “NEVER expose optional parameters in your frameworks”. Microsoft isn’t going to be exposing optional parameters in its frameworks. What we use instead is many different overloads of a method.


On the other hand, multiple overloads aren’t nice. When you’re calling a method, the intellisense is nicer if there’s only a single overload where it can show all the optional parameters. And multiple overloads are just bloat. We recognize the advantages of optional parameters, and think that the CLS guidelines are too strict for users, so C# 4.0 users won’t get a warning even when they use optionals in a class marked [CLSCompliant(true)].


In the scenario, though, to preserve binary compatibility, we’ve still had to keep both overloads, and you still see both candidates in the intellisense when you invoke the method. What we’d like is to <Obsolete> the old version of “fopen” so as to guide users onto the new version of “fopen”, and to change intellisense so that <Obsolete> members don’t show up.


But that doesn’t work! Any user who writes fopen(“file.txt”) will end up picking the <Obsolete> overload. So to make this work perfectly you’d need overload resolution to prefer candidates overloads without <Obsolete>. But now we’re getting onto shakey ground… it would be awful if a decorative attribute like <Obsolete> had real semantic effect, changing the behavior of your code (i.e. changing which overloads get picked). Also it would break backwards compatibility.


We also have to consider that the rules for overloaded method resolution are already extraordinarily complicated (and have evolved into this so as not to break backwards compatibility as new features were introduced). Inevitably, if we add tie-breaker rules to them, we’ll get unexpected behaviors in some corner case or other.


In the end, I don’t see a good answer here that solves every problem.


Thanks to Bill McCarthy for raising these points and spelling out the issues.


 


 


Provisional evaluation from the VB team: This is a decent idea, one that we should consider against the other decent ideas. Even if we can’t solve every problem, this change would at least provide some benefit (and bring parity with C#).

Comments (2)

  1. Dzonny says:

    Overloading by optional parameters sounds good. Several times I run into scenario that I wanted to add/change parameter of method with optional parameters and I couldn’t.

    But overload method resolution will get more complicated or even unclear.

    Sub a(ByVal x%, Optional ByVal y$ = Nothing)

    Sub a(ByVal x%, Optional ByVal y% = 0)

    a(7) ‘Which a is called? Or error – no a is most specific?

    An idea: Give hint to compiler by using <DefaultAttribute> or Default keyword for one of the overloads.

    <ObsoleteAttribute> makes thinks more complicated. I think we can let it have semantic effect because <Obsolete> is not just some attribute, it’s special attribute mentioned in CLI standard saying "The CLS specifies certain Custom Attributes and requires that conformant languages support them." If you opt for giving semantic effect to <ObsoleteAttribute>, there must still be way how to call obsolete method. For example

    <Obsolete>

    Sub Method(ByVal x%, Optional y% = 0) ‘1

    Sub Method(ByVal x%, Optional y% = 0, Optional z% = 0) ‘2

    Method(7) ‘2 called

    Method(7,9) ‘2 called

    #Pragma NoWarn 40008

    Method(7) ‘1 called, no warning

    #End Pragma

    ‘ –or–

    <Obsolete>Method(7) ‘1 called, no warning

    <Obsolete>Method(1,2,3) ‘Error: Sub Method(Integer, Integer, Integer) is not obsolete

    Other way can be giving hint to compiler, that I want to call overload with 3 attributes:

    Method(7,,) ‘2 called

    But this is ugly, because it defaults to obsolete overload, and it cannot deal with different type of optional argument.

  2. Kevin Ryall says:

    This is a tricky issue – our coding standards don’t generally allow optional parameters in the interest of following Microsoft’s recommendations, but there are times when optional parameters are much clearer and simpler than multiple overloads.

    Overloads are great when passing different types of a single parameter to the same method (cf. the overloads to Console.Write), but they are less suited when passing a variable number of parameters to a method. We use the standard practice of delegating to the next overload with the optional / missing value, and only implementing the final overload with the most parameters, but it’s still more code than using optional parameters – just to follow the recommended guidelines.

    The real pain comes when a method has a lot of parameters (often a code smell in itself, but sometimes unavoidable), and many of them are optional or have reasonable defaults. It’s not practicable to provide the number of overloads that would be required in these cases, so optional parameters are more attractive and ‘might’ pass a code review. We still don’t tend to use them though, because they don’t interact well with regular overloading. If would be great if they worked together seamlessly, but I appreciate that it’s difficult – and I don’t really have a suggestion for how to achieve this in away that would be completely safe…