Breaking changes and named arguments

Breaking changes and named arguments

Rate This
  • Comments 29

Before I get into the subject of today's post, thanks so much to all of you who have given us great feedback on the Roslyn CTP. Please keep it coming. I'm definitely going to do some articles on Roslyn in the future; the past few weeks I have been too busy actually implementing it to write much about it.


We introduced "named and optional arguments" to C# 4.0; that's the feature whereby you can omit some optional arguments from a method call, and specify some of them by name. This makes the code more self-documenting; it also makes it much easier to deal with methods in legacy object models that have lots and lots of parameters but only a few are relevant. The most common commentary I've seen about the "named" side of that feature is people noting that this introduces a new kind of breaking change. The commentary usually goes something like:

Suppose you are making a library for consumption by others. Of course if you change details of the public surface area of the library, you can break consumers of the library. For example, if you add a new method to a class then you might make an existing program that compiled just fine now turn into an error-producing program because a call to the old method is now ambiguous with a call to the new method. In the past, changing only the name of a formal parameter was not a "breaking change", because consumers always passed arguments positionally. Recompilation would not produce different results. In a world with named arguments though, it is now the case that changing the name of a formal parameter can cause working code to break.

Though that commentary is certainly well-intentioned and informative, it is not quite accurate. The implication is that this breaking change is something new, when in fact it has always been the case that a library provider could break consumers by changing only the name of a formal parameter. Visual Basic has always supported calling methods with named parameters. In the past, changing the name of a formal parameter was a breaking change for any consumers using Visual Basic, and that's potentially a lot of people. Now it becomes a potential breaking change for even more users. Adding a new feature to C# doesn't change the fact that the risk was always there!

Library providers should already have been cautious about changing the names of formal parameters, and they should continue to be cautious now.

 

  • I'm pretty sure you can still ignore parameter names in DLLs that export a C interface.

  • @NB: >I'm pretty sure you can still ignore parameter names in DLLs that export a C interface.

    I second your opinion, but I must pay attention to that for managed C# components targeting C#4+.

  • I've never found a use for this capability to begin with. It just makes the calling string that much longer. Overloading has always been the better choice in my opinion but overloading can still conflict with methods using optional parameters. One thing I don't like is how optional parameters are implemented in C# when the calling method is compiled. This is one of the reason I've never used named parameters in my code.

  • Eric> You are entitled to believe whatever bizarre thing you want;

    The same applies to you as well.

    Eric> however I note that in this case your belief is not justified by facts. Professional VB developers are an enormous market and have been for almost two decades now.

    I don't agree with that, and that is based on my experience, probably different from yours.

    My experiences are irrelevant. Your unjustified and false beliefs about the size of the VB market are based on your anectdotal experiences; my justified and true beliefs are based on data. We do extensive market research before we commit millions of dollars to infrastructure development. -- Eric

    From my point of view, VB developers *were* an enormous market for probably a decade (the 1990's and maybe early 2000's), before the advent of C# and .NET.

    I assure you that the pro dev VB market is more than large enough to justify a continued multi-year investment in development of new tools for VB pro devs. Rising tides lift all boats; .NET infrastructure makes VB better too.

    If you don't care about that market, that's your business, but I cannot afford that luxury. -- Eric

    Eric> for example, it seems unlikely that C# will ever have XML literals,

    I feel a relief for that :)

  • hype8912> I've never found a use for this capability to begin with. It just makes the calling string that much longer.

    Consider this case:

    // #1 - named arguments

    new StreamWriter(

     path: filename,

     append: true,

     encoding: someEncoding

    )

    // #2

    new StreamWriter(

     filename,

     true,

     someEncoding

    )

    #1 uses named arguments, and IMHO is too much verbose.

    The only semantic information added by #1 and kind of lacking in #2 is about the append mode.

    But the problem here is due to an unfortunate design decision in the StreamWriter constructor prototype.

    In fact, instead of using a bool, I'd have used a descriptive enum to specify append vs. overwrite mode.

    If an enum was used in this case, named arguments would have added nothing, because we'd have read some "append" in the calling code, instead of the more opaque 'true'.

    Another example that some people consider in favor of named arguments is for methods taking several parameters.

    But IMHO it is a design horror: if a method has too much parameters, this may be a red flag, and some refactoring may be needed. For example:

    void CalculateMotion(

     double positionX, double positionY, double positionZ,

     double velocityX, double velocityY, double velocityZ,

     double accelerationX, double accelerationY, double accelerationY

    )

    is a coding horror. The double's should be better packed in a more meaningful Vector3 class, and the method refactored something like this:

    void CalculateMotion(

     Vector3 position,

     Vector3 velocity,

     Vector3 acceleration

    );

    And then, when argument names are choosen carefully, there is no need to repeat the parameter names:

    CalculateMotion( aircraftPosition, aircraftVelocity, aircraftAcceleration );

    is just better then:

    CalculateMotion(

     position : aircraftPosition,

     velocity: aircraftVelocity,

     acceleration: aircraftAcceleration

    );

  • > But now with this new C# 4 feature you introduced a breaking change: I must be bothered with parameter names.

    You are making two conflicting arguments at the same time:

    1)  "This is a breaking change because it forces me to bother with parameter names."

    2)  "It did not use to be a breaking change because I chose not to care about VB developers or people using reflection."

    If you are in a position where you can selectively choose to ignore things, why not choose to ignore named parameters, just the same as you already chose to ignore VB and reflection users?  Or if you are in a position where you have to make things work for all possible consumers of your library, then you already had to bother with parameter names, for the reasons already given.  You can't have it both ways.

    >  Eric> Professional VB developers are an enormous market and have been for almost two decades now.

    >

    > I don't agree with that, and that is based on my experience, probably different from yours.

    This has nothing to do with subjective personal experiences.  The size of the VB developer market is easily quantified by counting the number of VB developers who exist in the world.  If you do that, you will find it is a very large number.

  • Named parameters are a great feature to help make code more readable.

    Now when are the C# authors going to finally get around to implementing ISO646 in C#? C and C++ have had it for a very long time: www.cplusplus.com/.../ciso646

    While I personally don't use the named operators for bit operations, I much prefer

    if (ready and enabled)

    to

    if (ready && enabled)

    Oh right.. it's too late. It would now be a breaking change to the language =(

  • Tergiver: if you like a more verbose syntax (with "and" instead of "&&", etc.) then you can use VB.

  • One of the great uses for named parameters is for making it clear what parameters you're passing. For example,

    CalculateMotion(velocity: aircraftVelocity, position: aircraftPosition, acceleration: aircraftAcceleration);

    is obviously correct, while

    CalculateMotion(aircraftVelocity, aircraftPosition, aircraftAcceleration);

    requires looking at the documentation or the declaration of CalculateMotion to tell if it's correct.

  • @Gabe - As PleaseDontLowerCSharpQualityBar points out, the clarity of of named parameters is only valid when you make a poor design choice in the creation of your method parameters.  If you go with the best design practice of never having 2 method parameters of the same type, then you avoid the need of named parameters.

    If the CalculateMotion method was defined as void CalculateMotion(Postion position, Velocity velocity, Acceleration acceleration) then there would be no problem.

    Just like you should never use bool as a parameter of a method, you should really never use double, int, string, etc. as they don't specify the MEANING of the parameter.  Always create a wrapper class that specifies the meaning of the parameter, and you can avoid the need for named parameters.

  • DRBlaise: Yes, it is much better to always create a wrapper class for every parameter type that is duplicated in any method call. That way rather than having something ridiculously confusing like

    CopyFile(SourcePath: srcPath, SourceFilename: srcFile, DestPath: dstPath, DestFilename: dstFile);

    you can have the much clearer

    CopyFile(new SourcePathName(new Path(srcPath), new Filename(srcFile)), new DestPathName(new Path(dstPath), new Filename(dstFile)));

    right?

    Sure, it requires extra typing, the creation of 4 wrapper classes, and additional overhead of memory allocation, wrapping calls, unwrapping calls, and garbage collection. But it's all worth it because, even though the wrapper classes' names can't ever change and the user of a method can't put the arguments in the order that makes sense in context, the method parameter names can be changed on a whim, right?

    Besides, you can just make the wrapper types structs to avoid the memory allocation and GC overhead, and hopefully your JIT will inline the wrapping/unwrapping calls you make. So really only the person writing the method and the person calling the method would have to pay for the overhead; the person running the code should be largely unaware of the wrappers so long as they don't notice the extra disk space, memory usage, and JIT time that the wrappers add.

    Of course if you're using a language like Java, you don't have structs, so each type has to be a class that requires memory allocation (though maybe the JIT can optimize that out?), and each wrapper class requires not only its own source file but also its own object file.

  • @Gabe - No, I think I prefer named parameters to defining wrapper classes/structs for all different parameter kinds. Seems to me they achieve pretty much the same in a generic way and without overhead as well. They might perhaps not be as powerful but I think I can live with that.

  • Slightly off topic, but I recently noticed that the use of named parameters has a noticable performance impact. What is the reason for that? Aren't optional and named parameters just caller side "compiler sugar"?

    Named and optional arguments can cause the compiler to introduce temporary varibles (to ensure that the side effects of argument computation happen in source code order, not in the order in which the arguments are put on the stack for the call.) Introducing extra temporaries can sometimes cause the jitter to generate less optimal code. I don't know of any other specific situation though; can you give me an example? -- Eric

  • It appears there is a bug in C# 4.0 with named arguments and struct members passed by reference:

    connect.microsoft.com/.../evaluation-of-named-by-ref-arguments-in-c

Page 2 of 2 (29 items) 12