What happens to C# 4 optional parameters when compiling against 3.5?

Here’s a method declaration that uses optional parameters:

public Path Copy(
Path destination,
bool overwrite = false,
bool recursive = false)

Something you may not know is that Visual Studio 2010 will let you compile this against .NET 3.5, with no error or warning.

You may be wondering (as I was) how it does that. Well, it takes the easy and rather obvious way of not trying to be too smart and just ignores the optional parameters. Well, actually optional parameters have always existed in VB.NET (and thus also in the CLR), it's just that C# has not been supporting them so far. So that code will compile to an optional parameter as they have existed for a while. For example, the overwrite parameter will have an Optional atribute and a DefaultParameterValue(false) attribute. Note that you could have added those attributes with previous versions of C#, it's just that the syntax was not that nice.

To summarize, if you target 3.5, Visual Studio will make your optional parameters usable from VB and any language that supports optional parameters. But if you are using C# 3.5 the above code is equivalent to:

public Path Copy(
Path destination,
bool overwrite,
bool recursive)

The parameters are not optional (no such thing in C# 3), and no overload gets magically created for you.

If you’re building a library that is going to have both 3.5 and 4.0 versions, and you want 3.5 users to have reasonable overloads of your methods, you’ll have to provide those yourself, which means that providing a version with optional parameters for the benefit of 4.0 users is not going to provide that much value…

Providing all of the following overloads will compile against both 3.5 and 4.0:

public Path Copy(Path destination)
public Path Copy(Path destination, bool overwrite)
public Path Copy(
Path destination,
bool overwrite = false,
bool recursive = false)

UPDATE: as several readers have pointed out, what I wrote was not quite accurate and the compiler does output an optional parameter even when targeting C# 3.5, it's just that C# 3.5 will not be able to take advantage of that extra information. I updated the post to reflect that. Thanks to all who exposed the error.

15 Comments

  • This isn't quite accurate. C# 4.0 optional parameter are like VB's optional parameters, not like overloads. When you compile against .NET 2.0 with Visual Studio 2010, the optional parameters are still encoded in the metadata and you can still use them from other languages (like VB) that understand them.

  • Ha! Thanks for the tip. Didn't think of that.

  • The problem with this though is for those people who convert their nullable types to optional parameters, they're now going to be required.

  • Bertrand, as you know, I forked Your FluentPath and made a version which compiles with "old" C# ...
    Although in there I have a (kind-of-a) solution , not just "feature cut off" ;o) It is based on "params".
    And it works with unchanged tests and sample app of the FluentPath.

    ///
    /// Named paramaters handler, for "legacy" C# code
    ///
    ///
    /// The documentation of the method where params is used is the only source of information about its arguments.
    /// Be sure to provide it.
    ///
    ///
    /// To use provide : params object [] argz
    /// as the last argument in the method. Then instantiate this class with optional
    /// name,value,name,value ... arguments, providing string name and value for each optional
    /// argument you need. For example:
    ///
    /// void method ( params object [] argz ) {
    /// dbj.Paramz p = new dbj.Paramz(argz, "name", "Bertrand", "age", 14, "msft", true ) ;
    /// // get the values by names provided, with indexing param
    /// string name = p["name"]; // casting is not required
    /// int age = p["age"] ; // because indexing operator returns dbj.Implicitor instance
    /// bool msft = p["msft"];
    /// }
    ///
    /// The code above simplifies implementation of "method()" that can be called with optional arguments.
    /// As in normal params usage the order of the argument in the params array is important. In the example
    /// above , if provided the first argument is "name", second is "age" and third is "msft".
    ///
    /// // provide "name" but not "age" or "msft" arguments
    /// method ("Joe") ;
    /// // can not provide "age" only, first argument is always taken as a "name"
    /// method ( 42 ) ;
    ///
    ///

  • Wouldn't you want to wrap the overloads in version specific #if/#endif?

    #define NET_Version3_5
    #if NET_Version3_5
    // some overloads
    #endif

  • Thanks Jeroen, I was about to ask if that was the case.

    Do you know if the optional parameters also act like that (compiled into the calling assembly) when compiling against 4?

  • @John: that's definitely an option. Personally I avoid directives and like to have the same code compile to both versions without them.

    @dbjdbj: it would have been nice to point to this post ;) What I don't like about your approach is that you can't have IntelliSense on the optional parameters, and there's a runtime cost to it. A little too JavaScripty.

  • Optional arguments are not the same as overloads in any way - magical or not.

    Public usage of optional arguments should never be encouraged, especially when omitting the perils – shame on you.

    When in doubt, never ever use optional arguments: http://paulomorgado.net/en/blog/archive/2010/04/16/c-4-0-named-and-optional-arguments.aspx

  • @BLR : I have done the "back pointer" to this post.

    Yes, my solution is of course having a runtime cost. But it requires no changes on the legacy callers side. It allows re-factored C#4 code to be compiled and used by legacy C#. Like (for example) my FluentPath fork "FP08", shows. FP08 can be used by everyone, not just (lucky?) VS2010 owners.

  • @dbjdbj: Thanks for the pointer. I've made available a 3.5-compiled version of
    FluentPath, and you should also be able to compile it using MSBuild.

  • @Prakash: I think what's confusing you is that the 4.0 compiler does what it can even if you're targetting 3.5 but if you look at the IL you'll see what really happens. Also, try compiling the same client code with the 3.5 compiler (from what I see you are using the 4.0 compiler to target 3.5).

  • @Roy: If you are using C# 4.0 compiler to compile the project with Optional Parameter it will works, Because Optional Parameter is the feature of C#4.0; So Whatsoever framework is targeted that does not make any difference.

    I have compile the code with C# 3.0 to target 3.5 Framework and it gives error; but compile the same code with C# 4.0 to target 3.5 Framework and it works.

    So, your Comment is wrong like compiling against 3.5 from Visual Studio 2010 will ignore the optional parameters and the above code is equivalent to:
    public Path Copy(
    Path destination,
    bool overwrite,
    bool recursive)"

    BECAUSE (I think) VS2010 uses C#4.0 compiler to compile the code so Optional Parameters will works anyways in VS2010.

    Pl reply if Im wrong :)

  • @Prakash: my name is Bertrand, not Roy. It does make a difference when you configure your project to target 3.5: you can then use the compiled dll in .NET 3.5 and VS 2008, which do not understand optional parameters. That's the whole point.

  • This isn't accurate at all. Below is some code from Visual Studio and then the output seen in reflector. It uses System.Runtime.InteropServices.OptionalAttribute to make this happen.


    public static bool Contains(this string text, string value, bool ignoreCase = false)
    {
    return text.IndexOf(value, 0, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) >= 0;
    }



    public static bool Contains(this string text, string value, [Optional, DefaultParameterValue(false)] bool ignoreCase)
    {
    return (text.IndexOf(value, 0, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) >= 0);
    }

  • Thanks to all who corrected me. I've updated the post.

Comments have been disabled for this content.