Freigeben über


Breaking changes and named arguments

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.

Comments

  • Anonymous
    November 07, 2011
    Changing a parameter name can also break dynamic invocation/ late binding including data binding so it has always had the potential to break many users.

  • Anonymous
    November 07, 2011
    I don't agree with Eric's conclusion. It's not at all clear to me what you disagree with. I reached two conclusions. First, that those who say that the introduction of named arguments to C# introduces a new kind of breaking change for library writers are mistaken; this is an existing and ongoing problem, not a new one. Do you disagree with that conclusion, and believe instead that this is a brand new problem that has never been seen before? Second, I concluded that library writers should continue to be careful to not introduce breaking changes. Do you disagree with that conclusion, and instead believe that library writers should be cavalier about causing breaking changes to their users? If you disagree with neither conclusion then what is thing that I actually said that you disagree with?  -- Eric IMHO, "polluting" C# with VB features of dubious quality is not a good thing. Whether or not the feature is a good one for C# is of course debatable. There are good reasons why the design team resisted adding named and optional arguments to the language for ten years. For me personally, the big reason to not do the feature is the amount of complexity it adds to the overload resolution logic, not because it exacerbates a long-existing fragility issue. Customer feedback over the last ten years, coupled with the continued popularity of legacy object models that expect the feature, was enough impetus to actually do the feature. All features have costs and benefits; we were finally convinced that the benefits to customers paid for the considerable costs. It was a good, pragmatic design decision driven by real customer requests. The feature is a fait accompli and has been for some time; complaining about it now is unproductive. The point of this article is not to debate the pros and cons of the feature, but rather to describe why it does not introduce a new kind of fragility. -- Eric If I wanted to interface with some IDispatch-based object-model, then I'd have used VB just to wrap those legacy libraries into a tiny .NET layer and then I'd have enjoyed using that layer from C#. Though that is a possible solution, it is not a very popular one with customers, not at all. Moreover, we have a stated goal of achieving greater parity between the C# and VB languages; this feature is part of that. -- Eric

  • Anonymous
    November 07, 2011
    This tiny VB "bridging" layer would have exposed the old legacy interface in a more modern form, e.g. using method overloads with a reasonable number of parameters, instead of fragile named arguments.

  • Anonymous
    November 07, 2011
    I think Eric makes a great point.  If you are exposing your dll to external users, then don't change parameter names or orders, ever!  Named arguments does not introduce a new problem.  This is not polluting the language.

  • Anonymous
    November 07, 2011
    haha.. nice try.. not it's not the same because VB doesn't matter. I'm not following your train of thought. What is not the same as what other thing? And to whom does VB not matter?  VB matters to me and to the many millions of people who write programs in it. -- Eric In c style languages parameter names never mattered in API design and was never a breaking change.. so who cares if VB had all along or not? C# never had it until this was introduced and that's the point.   I must again apologize for not at all following your point. My article is about how library writers need to be careful to not break their users, and must not assume that their users are using C#. Do you disagree with that? If so, why? -- Eric

  • Anonymous
    November 07, 2011
    @Darage, The point is, C# is a .NET language, and if you are writing a .NET library, you have to be prepared for consumers to be written in any .NET language, especially one as popular as Visual Basic.  Therefore, library writers who are complaining that this introduces a new breaking change were really missing the fact that they had been making potentially breaking changes all along for VB consumers of their library.

  • Anonymous
    November 07, 2011
    The comment has been removed

  • Anonymous
    November 07, 2011
    I'm actually glad C# introduced this because it makes the fact this is a breaking change more obvious. (the design guidelines have always noted this as a breaking change from the start). Of course, I mostly just love the fact I can write Directory.Delete(path, recurse: true) instead of /recurse:/ true now.

  • Anonymous
    November 07, 2011
    (Programming-language syntax) haters gonna hate (changes to programming-language syntax).

  • Anonymous
    November 07, 2011
    @Simon Buchan While I agree that Directory.Delete(path, recurse: true) is nicer than Directory.Delete(path, true) -- mostly because the latter makes no sense to an outside reader unless they are already familiar with the overloads of the Directory.Delete method -- I would much rather have had a consistent application of enumerations for these types of parameters. It bugs me that we have DirectoryInfo.GetFiles(pattern, SearchOption.TopDirectoryOnly) and in the same class we then have DirectoryInfo.Delete(false), where 'SearchOption.TopDirectoryOnly' and 'false' are semantically equivalent.  It would be so nice if these things were just a little more consistent (or if someone would ban parameters of type bool altogether).

  • Anonymous
    November 07, 2011
    I also think it should generaly not be necessary to change parameter names in public calls. If you find yourself in a situation where a parameter name is not what you wanted, then you shipped a library with a poorly code reviewed and poorly tested API. Don't blame that on the .NET framework or any .NET language. You'd have messed up yourself.

  • Anonymous
    November 07, 2011
    Changing method parameter names has always been a breaking change, I agree. One of the main features of writing software on top of the CLR is that assemblies can be used across language boundaries. If the authors of an assembly failed to incorporate CLS compliance it is their own responsibility. If your assemblies are CLS compliant you should be aware of VB consumers and how your C# code can break theirs. Also the other more obvious reason, as Shawn pointed out, is that changing a parameter name will break code that uses reflection on that parameter, probably not seen very often but a definite possibility. Introducing named arguments only made more room for a breaking change, it did not introduce a completely new breaking change.

  • Anonymous
    November 07, 2011
    Aren't named parameters just compile-time sugar? So when the caller produces their CLR assembly, the parameter name references cease to be significant. Parameters are slotted into the correct positions at the call site. This means that if a library changes a parameter name, it won't break already-compiled caller assembles. It will only break recompiles (from source) of callers. (This of course is not true of the reflection case, where the lookup by name happens at runtime). I would say that if you find yourself strongly needing to rename a parameter, it suggests that you're modifying the meaning of it. Doesn't that suggest a possibility that existing callers will now be broken anyway? They'll still be passing the correct type, but who knows it if will contain the correct value based on the new interpretation? Extreme example: a boolean parameter 'throwExceptionOnError' is changed to 'doNotThrowExceptionOnError'! Other examples may not be as extreme, but that might just make the new bugs more subtle. There are more trivial cases where you just want to rename a parameter to clarify the existing meaning. You might feel the same about a vague method name, but you're stuck with that too. In a multi-language platform like the CLR, you have to put exactly as much care into choosing parameter names as you put into choosing method names: they're part of the method name (in fact in Smalltalk and Objective C this is literally true!) REST urls are another case where the parameter names effectively appear embedded in the call. Before C# 4 I used to grumble to myself about a library called OpenRasta, which is a RESTful webserver framework in which method parameters have significance in how they map to parameters in the URL. It seemed wrong to me at the time - but then, I was ignorant of the VB.NET thing.

  • Anonymous
    November 08, 2011
    I look forward to this feature.  Named arguments is a feature that I have missed since I stopped using Ada.

  • Anonymous
    November 08, 2011
    First: I appreciate the time you spent reading my comment and answering me. I disagree with the first conclusion.  I wrote some DLL's in C++ exposing a pure C interface, and I used them from C#. I've never thought that parameter names were important; as I've never bothered with that both in my native C++ code and in my managed C# code. Ignorance of a problem does not make it go away. Now you are no longer ignorant of the problem, but the problem was always there. It did not just suddenly appear. -- Eric I've never considered VB.NET clients, I don't want to waste my time with VB.NET. So you're saying that because potential consumers of your libraries might use a language that you find distasteful for some personal aesthetic reason, it's fine with you to break them with impunity? Perhaps you are in a market position where you can afford to impose unnecessary costs on potential consumers; I'm sure that's an enviable position, but just because you have the wherewithall to offend potential customers does not mean that you should. I assure you that I, and millions of other developers, am not in that position. My VB-using customers are extremely important to me and my team works hard to ensure that they do not have a bad experience when upgrading libraries. That includes ensuring that public surface area changes do not cause breaks. -- Eric BTW: I do consider C# the king language for managed code; after VB6 - the "COM VB" - VB programmers flew in flocks to C#; when the only "games in town" were C++/MFC and classic VB, there was a real market for RAD and VB; but with the introduction of .NET and C#, I see no market for professional VB.NET development. I believe the professional VB.NET programmers are a very small number these days. You are entitled to believe whatever bizarre thing you want; 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. We would not be spending millions of dollars on VB infrastructure if this were a tiny market. -- Eric Just out of curiosity: do you use VB.NET in Microsoft? In what successful Microsoft software is VB.NET used? The most obvious example to me, because I work with it every day, is the VB runtime library itself, and the VB portions of the Roslyn project. But we at Microsoft are not selling VB to ourselves; how we use it internally is completely irrelevant. We're selling VB to the market. VB is heavily used in both government and business applications worldwide. Those are the customers that we do not want to break. -- Eric But now with this new C# 4 feature you introduced a breaking change: I must be bothered with parameter names. And about this language parity with VB.NET, I don't think this is a good value. Let's get good things from other languages, but please do filter stuff. ...Are you considering making C# case-INsensitive to achieve VB language-parity ? :) Of course we are doing so judiciously; for example, it seems unlikely that C# will ever have XML literals, and it seems unlikely that VB will ever have unmanaged pointers. But going forward we will not be, say, adding asynchrony to C# and not to VB, or vice versa. -- Eric

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

  • Anonymous
    November 08, 2011
    The comment has been removed

  • Anonymous
    November 09, 2011
    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.

  • Anonymous
    November 09, 2011
    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 :)

  • Anonymous
    November 09, 2011
    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 );

  • Anonymous
    November 10, 2011
    > 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.
  • Anonymous
    November 10, 2011
    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 =(

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

  • Anonymous
    November 13, 2011
    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.

  • Anonymous
    November 13, 2011
    @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.

  • Anonymous
    November 14, 2011
    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.

  • Anonymous
    November 14, 2011
    @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.

  • Anonymous
    December 08, 2011
    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

  • Anonymous
    December 29, 2011
    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