Answered by:
Some thoughts about default parameter for C#

Question
-
I've been troubled by this problem since I started to use C#. I always try to write classes that are easy to use. When I try to write some methods that takes several parameters, I found it was really troublesome to write different overloads. I had to give up some overloads, because there are just too many combinations, when the parameter list is long.
And I thought, why not default parameters?
Anders Hejlsberg explained in an interfiew that they didn't include default parameter because they think it was not necessary. They think it's just feature that make programmer a bit easier, but not some feature that make the language more powerful.
Well I say if you can make us easier with very little effort, why not?
Here is my thought.
void Test()
{
//all combinations are allowed
//all default
Foo(default int, default float, default string);
//one default
Foo(1, default float, default string);
Foo(default int, 2.0f, default string);
Foo(default int, default float, "I want default parameters.");
//even the middle one
Foo(10, default float, "Please consider my suggestion.");
}
void Foo(int i = 0, float f = 1.0f, string s = "default")
{
System.Console.WriteLine("{0},{1},{2}", i, f, s);
}
Disadvantage:
1. A bit tedious to call, because even if you don't put any actual parameters, you have to write down all the types.
Advantages:
1. Unlike C++ default params, this allows all combinations
2. No signiture conflict or ambigurity. The whole signiture is matched when calling a method with default paramters, because type must be specified if the parameter is missing.
3. No runtime overhead, because everything can be determined at compile time.
Feasibility. I don't think it's difficult to implement (in later version of C#) because:
1. No additional key word is introduced. "default" is already a key word in C#
2. No ambiguity. It doesn't conflict with any existing C# language semantics
So, WHY not?Monday, December 19, 2005 2:38 AM
Answers
-
You are correct that I only responded to the last third of your post. If you want I'll respond to your other two recommendations.
In the first case you are assuming that the default parameter value is somehow stored in the metadata and therefore accessible to the compiler. This is not the case in C++ (if that is where you were trying to go). If the default parameter value has to be stored in the metadata in order to support compilation (because we compile against metadata not source files when referencing other assemblies) then why not just extend the CLR to take default parameter values into account? This would fall back into the case I mentioned in my last post about having to push the default parameters to the runtime although not with as much runtime overhead. Of course the biggest issue here is language interoperability. Most languages do not support default parameters and therefore it wouldn't be a CLS compliant feature. Therefore I would be required to mark any method that uses a default parameter as non-CLS compliant. By definition any derived classes would also have to mark the method as non-CLS compliant even if they otherwise would. CLS compliance is important to me so I would have a hard time with this.
In the second case you gave I find it too restrictive. One class anywhere in an inheritance hierarchy would prevent the use of default parameters in other classes on the same method (deriving from the same type). This would be further compounded if the base class had a method that was later modified to use a default parameter. The difficulty here is that default parameters are not considered to be part of a function's signature. Therefore the process of determining that a public definition (the class method in this case) has to be changed.
As I stated earlier given the various "hacks" and nuiances of getting default parameter values to work for the occasional time they are useful just isn't worth it. I find the way the CLR works now to be perfectly acceptable because I get default parameter values (through runtime invocation) without any side effects that I have to worry about (like the inheritance hierarchy of a type). The few additional lines of code that I have to type to give me this functionality more than makes up for the fact that it is missing. I use to use C++ default parameters a lot but now that I've switched to C#'s variation and have gotten use to it I find it the more sound way to go. You never know, the C# team might be thinking about adding this feature at some future time...
Michael Taylor - 12/20/05
Wednesday, December 21, 2005 2:43 AM
All replies
-
The biggest issue with default parameters is that they are, in fact, compile time defaults and not runtime. Imagine the following scenario:
public class BaseClass
{
public virtual void Foo ( string name, int value = 10 ) { }
}public class DerivedClass : BaseClass
{
public override void Foo ( string name, int value = 20 ) { }
}
When someone calls Foo("test") what is the value of value? The answer is that it depends on how the call is made. This is because the compiler determines the value to call and not the runtime. As an example if you later define a method like so
void CallFoo ( BaseClass cls, string name )
{cls.Foo(name);
}
Then because the compiler determines the default value it will ignore the virtual (that is a runtime attribute) and use BaseClass's default of 10. But at runtime if I actually pass a DerivedClass object then it'll call DerivedClass.Foo with the base class'es default value. Needless to say this causes confusion.
The only way to deal with this is to require that defaults be determined at runtime but then this makes it inefficient. Therefore the C# team decided (rightfully in my opinion) that it is better to require that if you want default parameters you must right methods that pass the right values. This permits the runtime determination of default values instead of compile time.
//In BaseClass
virtual void Foo ( string name ) { Foo(name, 10); }
//In DerivedClass
override void Foo ( string name ) { Foo(name, 20); }
Now the overridden version is called correctly and with the expected default value. I agree that it can be annoying having come from a C++ background myself but I understand completely their view.
Michael Taylor - 12/19/05
Monday, December 19, 2005 1:57 PM -
You have a very good point. I didn't think think of that.Tuesday, December 20, 2005 4:10 AM
-
Actually in C++, the same issue presents.
And C++ handles it this way, which is not very logical because default param is determined at compile time but method bounding is determined at runtime.
class A{
public:
virtual void m(int d = 10) {cout << "A:" << d << endl; }
};
class B : A{
public:
void m(int d = 20) {cout << "B " << d << endl; }
};
void main(void)
{
B* b = new B();
A* a = (A*)b;
a->m();
b->m();
}
Result:
B: 10
B: 20Tuesday, December 20, 2005 4:30 AM -
To make the binding and default param value consistant,
I can take something like this:
For an overrided method, if a parameter already has a default value, no new default values are allowed.
So the default value can always be determined at compile time.
class A
{
public virtual void M(int p1, int p2 = 0)
{
System.Console.WriteLine("A,{0},{1}", p1, p2);
}
}
class B : A
{
public override void M(int p1 = 0, int p2 = default)
{
System.Console.WriteLine("B,{0},{1}", p1, p2);
}
}
class Test
{
public void Test()
{
B b = new B();
A a = b;
a.(1, default int); //output: B,1,0
b.(1, default int); //output: B,1,0
a.(default int, default int); //compiler error, no matched method
b.(default int, default int); //output: B,0,0
}
}
This should be good enough in most cases.
If different default params for overrided methods are required, the parent method shouldn't contain any default value.
class A
{
public virtual void M(int p1, int p2)
{
System.Console.WriteLine("A,{0},{1}", p1, p2);
}
}
class B : A
{
public override void M(int p1 = 0, int p2 = 0)
{
System.Console.WriteLine("B,{0},{1}", p1, p2);
}
}
class C : A
{
public override void M(int p1 = 0, int p2 = 1)
{
System.Console.WriteLine("C,{0},{1}", p1, p2);
}
}
class Test
{
public void Test()
{
B b = new B();
A a = b;
a.(1,1); //output: B,1,1
a.(1, default int); //compiler error, no matched method
b.(1, default int); //output: B,1,0
a.(default int, default int); //compiler error, no matched method
b.(default int, default int); //output: B,0,0
}
}
If a base class was written by some one else with default params in a virtual method. And you want set your own default params. This can be done, but the programmer should be very aware of what's going on, because the dynamic binding is gone.
class A //written by someone else, cannot be changed
{
public virtual void M(int p1 = 0, int p2 = 0)
{
System.Console.WriteLine("A,{0},{1}", p1, p2);
}
}
class B : A
{
public new void M(int p1 = 1, int p2 = 1)
{
System.Console.WriteLine("B,{0},{1}", p1, p2);
}
}
class Test
{
public void Test()
{
B b = new B();
A a = b;
a.(1,1); //output: A,1,1
a.(1, default int); //output: A,1,0
b.(2, default int); //output: B,2,1
a.(default int, default int); //putput: A,0,0
b.(default int, default int); //output: B,1,1
}
}
Tuesday, December 20, 2005 5:03 AM -
But then you'd fall into the pitfall that already plagues new. Your overload would only work with classes that are aware of B which defeats the whole purpose of inheritance.
public void CallM ( A cls )
{cls.M(); //output: A,0,0
}As you can see you are trying to bend the language to meet the compile-time needs of constants with the runtime behavior of inheritance. This just isn't going to work without causing some issues and confusion. The C++ version is still confusing to a lot of people. The ideal solution would be to postpone default parameters until runtime and then figure it out then but this would greatly complicate function lookups and would require that all methods be treated as effectively virtual (to force the runtime to look up the appropriate function with defaults) which would have a dramatic impact on performance. Therefore I still think the existing solution is the right way to go because it gives us runtime defaults without the overhead of virtual functions.
Michael Taylor - 12/20/05
Tuesday, December 20, 2005 2:31 PM -
I think you didn't read my post carefully enough.
public void CallM ( A cls )
{cls.M(); //output: A,0,0
This will occur only for the third case I discussed, where the "new" keyword was used instead of "override". I clearly stated that the dynamic binding would be gone, but it's just what the "new" keyword does.
}Wednesday, December 21, 2005 1:59 AM -
You are correct that I only responded to the last third of your post. If you want I'll respond to your other two recommendations.
In the first case you are assuming that the default parameter value is somehow stored in the metadata and therefore accessible to the compiler. This is not the case in C++ (if that is where you were trying to go). If the default parameter value has to be stored in the metadata in order to support compilation (because we compile against metadata not source files when referencing other assemblies) then why not just extend the CLR to take default parameter values into account? This would fall back into the case I mentioned in my last post about having to push the default parameters to the runtime although not with as much runtime overhead. Of course the biggest issue here is language interoperability. Most languages do not support default parameters and therefore it wouldn't be a CLS compliant feature. Therefore I would be required to mark any method that uses a default parameter as non-CLS compliant. By definition any derived classes would also have to mark the method as non-CLS compliant even if they otherwise would. CLS compliance is important to me so I would have a hard time with this.
In the second case you gave I find it too restrictive. One class anywhere in an inheritance hierarchy would prevent the use of default parameters in other classes on the same method (deriving from the same type). This would be further compounded if the base class had a method that was later modified to use a default parameter. The difficulty here is that default parameters are not considered to be part of a function's signature. Therefore the process of determining that a public definition (the class method in this case) has to be changed.
As I stated earlier given the various "hacks" and nuiances of getting default parameter values to work for the occasional time they are useful just isn't worth it. I find the way the CLR works now to be perfectly acceptable because I get default parameter values (through runtime invocation) without any side effects that I have to worry about (like the inheritance hierarchy of a type). The few additional lines of code that I have to type to give me this functionality more than makes up for the fact that it is missing. I use to use C++ default parameters a lot but now that I've switched to C#'s variation and have gotten use to it I find it the more sound way to go. You never know, the C# team might be thinking about adding this feature at some future time...
Michael Taylor - 12/20/05
Wednesday, December 21, 2005 2:43 AM -
What you are defining here, I think, is a template in C++if I am not mistaken. It seems that what you are doing is overloading a parameter that is a primitive type. Generics handles this in most cases in .NET 2.0 I would suggest looking into design pattern instead on overloading one method so many times. Polymorphism would be a more viable solution to your overloading woes; using aggregation, instead of inheritance, of course. This would afford you the luxury of dynamic binding(at run time) instead of the strong type found in primitive data types across nearly all OOL's. A good question to ask yourself is: what is a primitive data type,, or better yet how is it defined? Answer that an you will find the answer Hjels or Hells or Hells Bells should have given to you instead of that generic crud! Of course it would make the programmers life easier, but why not do it........think about it.....clue is to answer the question I posed above and apply to your solution. I can see it in you example code solution. In closing I will say your soluion is credidle to an extent, but is it the most efficient, effective answer to the problem? Truthfully, I really do not know, but studying programming languages, not learning a language per say, gives me an idea, right or wrong, that I would hate to be the guy, or on a team that had to figure out all the possibile combinations, in all the different scenarios, to make writing a little code easier for a programmer. In this regard, I agree with Hjel or whoever(and yes I know who he is) overwhelmingly! There is absolutely "no" need for default value in C#. Oh, did you answer that question yet? I would most definately be interested in hearing what it is? Sorry if I sound like a know it all, but as a programmer we only wish that somehow we could go back and fix the mess created by strong-binding primitive data types, which is by the way is done at compile time, and do all the type-binding dynamically(or at run-time). Would sure make things so much easier and the language much more scalable. Now I must get back to my project, in which tonight, I am going to attempt to do what you are attempting to fix, dynamically bind all my data types. Wish me luck! Take care, sharpbart
Thursday, March 30, 2006 3:35 AM