tags:

views:

196

answers:

3

Suppose I have an existing assembly, and some of the classes have overloaded methods, with default behavior or values assumed for some of those overloads. I think this is a pretty typical pattern;

Type2 _defaultValueForParam2 = foo;
Type3 _defaultValueForParam3 = bar;

public ReturnType TheMethod(Type1 param1)
{
   return TheMethod(param1, _defaultValueForParam2);
}
public ReturnType TheMethod(Type1 param1, Type2 param2)
{
   return TheMethod(param1, param2, _defaultValueForParam3);
}
public ReturnType TheMethod(Type1 param1, Type2 param2, Type3 param3)
{
   // actually implement the method here. 
}

And I understand that optional params in C# is supposed to let me consolidated that down to a single method. If I produce a method with some params marked optional, will it work with downlevel callers of the assembly?


EDIT: By "work" I mean, a downlevel caller, an app compiled with the C# 2.0 or 3.5 compiler, will be able to invoke the method with one, two or three params, just as if I had used overloads, and the downlevel compiler won't complain.

I do want to refactor and eliminate all the overloads in my library, but I don't want to force the downlevel callers using the refactored library to provide every parameter.

+1  A: 

In c# 4.0, when an optional parameter is omitted, a default value for that parameter is substituted, to wit:

public void SendMail(string toAddress, string bodyText, bool ccAdministrator = true, bool isBodyHtml = false)
{ 
    // Full implementation here   
}

For your downlevel callers, this means that if they use one of the variants that is missing parameters, c# will substitute the default value you have provided for the missing parameter. This article explains the process in greater detail.

Your existing downlevel calls should all still work, but you will have to recompile your clients in c# 4.0.

Robert Harvey
So what you are saying is, I *cannot* use [optional params] to refactor my assembly, if I Want to support downlevel consumers of the assembly, unless I am happy with the downlevel guys specifying all params for every method invocation.
Cheeso
I clarified my answer. Sorry about the confusion.
Robert Harvey
Isn't that default value substituted by the *caller*, not the *callee*? That would mean that the compiler for the calling code would have to understand the default parameters, so pre-4.0 consumers will *not* work, at least without specifying all the arguments.
P Daddy
The default value is baked into the code when the application is compiled. Perhaps my example is not clear. defaultValue2 and defaultValue3 are constants.
Robert Harvey
I updated my answer with a better example, and a link to an article that explains the process.
Robert Harvey
But they're baked into the *caller* as constants. This is how it works in C++, at least. Think of it, the caller pushes the arguments on the stack, and the callee reads them from there. If the caller pushes fewer arguments than the number of formal parameters, the callee would have no way of knowing this, and would read stack data that wasn't part of the arguments list. Therefore, the callee *cannot* have the onus of substituting the default values. It *must* be done by the caller. The caller must know to look at the metadata to find the constants to use as defaults.
P Daddy
You'll have to take my word for it, I guess. The compiled application substitutes the provided default value for the parameter if it is omitted. The programmer dictates this value in the compiled code, not the caller. Check out the link to the article I provided. It even shows how the IL is generated.
Robert Harvey
http://geekswithblogs.net/michelotti/archive/2009/02/05/c-4.0-optional-parameters.aspx
Robert Harvey
I'm sorry, this article doesn't prove your point. In fact, it seems to confirm mine. It says that the defaults are compiled in as metadata, and if you read near the end, it even demonstrates that a call with missing arguments will compile to the same IL as if the default values had been passed explicitly. Ergo, the compiler for the caller **must** understand optional parameters.
P Daddy
based on the back-n-forth, it's clear that there is some confusion, so I'm glad I asked. The way I see it is this: does the c# 4.0 compiler produce secret overloads as a way to support default params? in other words when I label params as optional, is it essentially producing overloads in the IL, similar to the actual coding pattern we now use with C# 3.0? If so, then downlevel callers are good to go. If NOT, then not. The example given - omitting the Missing.Value's when calling an Office PIA - is not a downlevel caller. It is a C# 4.0 caller.
Cheeso
It would appear that your clients have to be recompiled in c# 4.0. Further, if the default constants are changed, the clients have to be recompiled again. "Once you expose a default parameter value on a public method, you can never change it without recompiling all clients that depend on it. For library writers, this never means never ever. If you need the flexibility of changing defaults afterwards, consider providing overloads instead." http://dotnet.dzone.com/news/c-40-feature-focus-part-1-opti
Robert Harvey
A: 

Well, I think that if you replace all 3 methods by a single method with optional parameters, the code that uses your library will still work, but will need to be recompiled.

Thomas Levesque
Not true. the downlevel clients will not work.
Cheeso
When I posted that answer, you had not specified that the downlevel clients where compiled with .NET 2.0 or 3.5... so it *was* true before you edited your post ! Downvoting because my answer *became* incorrect after your edit seems a bit unfair...
Thomas Levesque
+1, giving your downvote back.
Robert Harvey
+1  A: 

I haven't read the docs on the new language standard, but I would assume that your pre-4.0 callers will have to pass all declared parameters, just as they do now. This is because of the way parameter-passing works.

When you call a method, the arguments are pushed onto the stack. If three 32-bit arguments are passed, then 12 bytes will be pushed onto the stack; if four 32-bit arguments are passed, then 16 bytes will be pushed onto the stack. The number of bytes pushed onto the stack is implicit in the call: the callee assumes that the correct number of arguments was passed.

So if a function takes four 32-bit parameters, it will look on the stack at the 16 bytes preceding the return address of the caller. If the caller has passed only 12 bytes, then the callee will read 4 bytes of whatever was already on the stack before the call was made. It has no way of knowing that all 16 expected bytes was not passed.

This is the way it works now. There's no changing that for existing compilers.

To support optional parameters, one of two things has to happen:

  1. The caller can pass an additional value that explicitly tells the callee how many arguments (or bytes) were pushed onto the stack. The callee can then fill in the default values for any omitted parameters.
  2. The caller can continue passing all declared parameters, substituting default values (which would be read from the callee's metadata) for any optional parameters omitted in the code. The callee then reads all parameter values from the stack, just as it does now.

I suspect that it will be implemented as in (2) above. This is similar to how it's done in C++ (although C++, lacking metadata, requires that the default parameters be specified in the header file), is more efficient that option (1), as it is all done at compile time and doesn't require an additional value to pushed onto the stack, and is the most straightforward implementation. The drawback to option (2) is that, if the default values change, all callers must be recompiled, or else they will continue to pass the old defaults, since they've been compiled in as constants. This is similar to the way public constants work now. Note that option (1) does not suffer this drawback.

Option (1) also does not support named parameter passing, whereby given a function declared like this:

static void Foo(int a, int b = 0, int c = 0){}

it can be called like this:

Foo(1, c: 2);

Option (1) could be modified to allow for this, by making the extra hidden value a bitmap of omitted arguments, where each bit represents one optional parameter. This arbitrarily limits the number of optional parameters a function can accept, although given that this limitation would be at least 32, that may not be such a bad thing. It does make it exceedingly unlikely that this is the actual implementation, however.

Given either implementation, the calling code must understand the mechanics of optional parameters in order to omit any arguments in the call. Additionally, with option (1), an extra hidden parameter must be passed, which older compilers would not even know about, unless it was added as a formal parameter in the metadata.

P Daddy
So would it be fair to say that Cheeso will be alright if he recompiles his clients in c# 4.0?
Robert Harvey
Of course. By design.
P Daddy
yes, I think that goes without saying. But the clients are not all "mine" and I can't force them to use C# 4.0. So.
Cheeso
I am a little surprised that it would require this degree of understanding of c# compiler internals to make proper use of the language.
Robert Harvey
@Robert: You actually don't need to know detailed internals to *use* the language, although knowing how things work under the hood helps you make better use of the language. It sometimes *is* necessary to know the dirty details though, if you need to know **why** something works the way it does.
P Daddy
@Cheeso: If you must support pre-4.0 clients, then you must use pre-4.0 features. I still have 1.1 libraries I must maintain—in parallel with newer versions—to support consumers stuck on that version of the framework.
P Daddy