[Proposal]: Non-Compile Time Constant Default method parameter values #3947
Replies: 13 comments 10 replies
-
Which overloads should be generated in the following case: void M(List<int> a = new List<int>(), List<int> b = new List<int>()) {...} |
Beta Was this translation helpful? Give feedback.
-
As @YairHalberstadt highlights overloads cannot be generated for methods with default parameter values due to the fact that they will create collisions where two such overloads will have the same signature. Even if that was not the case it doesn't take too many optional parameters before you result in an explosion of overload methods. |
Beta Was this translation helpful? Give feedback.
-
A lowering method that could work would be this: void M(List<int> a = new List<int>(), List<int> b = new List<int>()) {...} Is lowered to:
And then at the callsite generate: M(DefaultValue<>M<>List`1<>List`1<>0, DefaultValue<>M<>List`1<>List`1<>1) However as you can see this would be much more complicated to design and implement... |
Beta Was this translation helpful? Give feedback.
-
Scala has an interesting way of enabling expressions for default values for optional parameters. It emits another method on the type declaring the method which contains the expression. When you omit the optional parameter at the call site the compiler automatically emits an invocation of that method to obtain the default parameter value which it then embeds into the call. So you write something like this: class Program
{
void Foo(DateTime date = DateTime.Now) => Console.WriteLine(date);
void Bar()
{
Foo();
}
} and the compiler emits (something like) this: class Program
{
void Foo([Opt("Foo_date")] DateTime date) => Console.WriteLine(date);
DateTime Foo_date => DateTime.Now;
void Bar()
{
Foo(Foo_date);
}
} While it does enable these expressions it is also brittle in a couple of ways. It exposes the negotiation of the default parameter to the public surface of the type, and changes to the default values can break existing consumers. That's currently not a problem as the default value is only negotiated by the compiler and embedded directly in the call. |
Beta Was this translation helpful? Give feedback.
-
Also duplicate of #1702 |
Beta Was this translation helpful? Give feedback.
-
void M(List<int> a) => M(a, new List<int>());
voic M() => M(new List<int>(), new List<int>()); Overload generation follows the current way method calls with default parameters are determined. void Foo(int a = 0, int b = 1) {
Console.WriteLine($"{a}, {b}");
}
Foo(4, 5); // 4, 5
Foo(4); // 4, 1
Foo(); // 0, 1 Because we know that calling with one argument would always assign that value to the first of the two parameters with the same type, we'd generate only the overloads that follow this pattern. @YairHalberstadt @HaloFour As you can see, no collisions. |
Beta Was this translation helpful? Give feedback.
-
That doesn't take into account named parameters and would make the following impossible: Foo(b: 4); // 0, 4 The compiler can't just skip those overloads. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour Good point. I suppose we could do some name mangling and do lowering at the callsite as well.... not as elegant, but possible. void Foo(int a) => Foo(a, 1);
void Foo_named0(int b) => Foo(0, b);
void Foo() => Foo(0, 1); And use the |
Beta Was this translation helpful? Give feedback.
-
@digitalcreature there are plenty of methods with 20 optional parameters. That would require generating over a million methods! |
Beta Was this translation helpful? Give feedback.
-
@YairHalberstadt we would only generate overloads for non-constant default parameters; I can't imagine many use cases that would require that many nonconst optionals. Also having 20 parameters in the first place seems like bad practice, I doubt there can really be that many of them. |
Beta Was this translation helpful? Give feedback.
-
That could make changing the default value a breaking change. There are a lot of cases where it's not obvious whether changing the expression would lead to such a break, such as
The reason support for optional parameters was added to C# at all was to improve the story with Office interop, where it's normal for methods to have a large number of optional parameters. From what I understand the teams originally tried to solve the problem with tooling that would generate overloads but that caused all of the problems listed above and was determined to be an untenable solution. Adding optional parameters (and a few other language features) was a compromise. |
Beta Was this translation helpful? Give feedback.
-
why not just have the compiler treat optional parameters similar to nullable ones and just assign the expression to it right before executing the beginning of the method? void Example(TestA a = new TestA())
{
//do method
} would basically be compiled down to.. void Example(TestA? a = null)
{
if (a == null ) a = new TestA();
//do method
} of course we would still want to obey real nullables, so assigning something like null or a nullable wouldn't be possible to non nullables void Example(TestA a = null) //warning or error whatever
{
//do method
} |
Beta Was this translation helpful? Give feedback.
-
There is no reason for this not to be supported at this point. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Non-Compile Time Constant Default method parameter values
Summary
Right now, when declaring default values for method parameters, the expression must be a compile-time constant. Sometimes it would be nice to be able to declare a default value that is evaluated at call-time, such as instantiating a default object or struct.
Motivation
The current workaround for this is to use a method overload, or to check for a compile time default value such as null:
The former introduces a branch whose evaluation is known at compile time based on call site, but needs to be evaluated at every call. It also prevents passing the value null when it is desired as it will always be overwritten.
The latter is simple enough with one parameter, but it gets unwieldy with additional parameters, as well as 2^n overloads for a method with n parameters with non-comptime constant values. This is excessive boilerplate.
Detailed design
Implementation should be trivial with lowering. Simply generate the needed overloads when these such parameters are encountered.
Beta Was this translation helpful? Give feedback.
All reactions