Init-parameters: methods with initializers #9528
Unanswered
amal-stack
asked this question in
Language Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Motivation
C# offers a convenient syntax for object and collection initialization, by adding a set of curly braces after a constructor invocation. This post-construction initializer list can:
Add
methods on the created instance (if present as a member or extension).In this case, this is called a collection initializer instead; C# defines it as a distinct construct altogether and it cannot be combined with object initialization. (it's either
Add
calls (collection initializer) or property/indexer setting (object initializer)).For example, adding headers via the
DefaultRequestHeaders
in anHttpClient
:With required and
init
-only properties, their use is even more encouraged and enforced. Unfortunately, they can only appear just after a constructor invocation and cannot be used anywhere else. They would be especially useful if they were allowed to appear after a method invocation. This would bring in many benefits like concise factory methods and better DSL capabilities just to name a few.Solution: init-parameters
Introduce a new parameter modifier, called
init
. When a method has an init parameter, the caller can provide its value using an initializer block, similar to how constructors work.where
PersonInfo
is the following:Since the
info
parameter in theCreate
method above is marked withinit
, it can be called as:Or with the parentheses omitted when no other parameters exist:
The compiler can replace this call with a parameterless constructor invocation of the
init
parameter followed by the provided initializer list:Other parameters can also exist for a method using
init
parameters:which can be invoked as:
We can also allow collection types or types with an
Add
method to be marked as aninit
parameter:which can then be invoked as:
Rules
init
-only properties can be the same as other initialization lists, as per the properties declared in the type that is marked as aninit
parameter.init
parameter per method and it should preferably be the last one.init
parameters (they already support initializer lists).Benefits
Factory methods can use initializer lists just like constructors, even if they are async and return a
Task<T>
, a subtype or any type.Provides better DSL capabilities. For instance, consider the following:
This can be rewritten as:
Provides a way to support parameters that are always "named":
C# currently has named parameters but specifying names are completely optional and there is no way to force the client to always specify parameter names. Languages like Python and Dart provide a syntax for named-only parameters. Initializer lists provide a natural way to achieve required named parameters as the property names are always required.
Allows the notion of "optional" parameters: since non-required properties need not be set.
Improved consistency with modern object modeling using
required
andinit
.Real-world examples
Here are some examples of APIs where incorporating init parameters can improve readability and user experience:
1. Json serialization
Before:
The
JsonSerializerOptions
can be markedinit
for more concise syntax.After:
2. Service collection extensions
Before:
After:
3. HttpClient configuration
Before:
After:
Potential Challenges
1. Overload Ambiguity
How would method overloads that differ only on the type of their
init
parameters be handled?Consider the following case where two
Create
overloads exist:Now at the call site:
Solution:
In such cases where an ambiguity exists, the
init
parameter can be explicitly supplied:2. Lambda/delegate considerations
The nested closure pattern is extremely common and heavily used all over .NET. For instance, see the 2nd example in the "Real world examples" section. To support the "after" variant, the method signature, instead of this:
the method signature will have to be this:
Solution:
init Action<T>
parameters. Since, accepting anAction
delegate is a common pattern, we could allow a method defined like this:to be called as:
where the property assignments set the properties on the parameter passed to the
Action
delegate instead of creating a fresh instance. So, in case of aninit Action<T>
parameter, the above is equivalent to:Allowing this also brings in other advantages, like allowing modification of an existing object instead of creating a fresh object using the parameterless constructor of the
init
parameter. This useful if we want to allow overriding values or configuring a partially initialized object.See also: #2781 (Kotlin/Swift style trailing closure blocks)
3. Requires a type
This syntax requires introducing a new type that will be marked as the
init
parameter.Solution: We could allow something like an inline type declaration:
4. Generic init parameters
Should generic init parameters be allowed?
5. Using
dynamic
We could allow
dynamic
/object
init-parameters which could allow passing an anonymous type in an initializer list.Beta Was this translation helpful? Give feedback.
All reactions