Repository navigation
[Discussion]: Argument Forwarding #10308
Replies: 8 comments 8 replies
|
IMO it feels like something better suited to a snippet expansion in an IDE than a language feature. I'd want to see and read the full expansion, not have to evaluate in my head how that will play out. |
|
Thanks, that readability concern makes sense. The main reason I was thinking of this as a language feature rather than only an IDE snippet is that a snippet helps at the moment code is written, but it does not keep the forwarding call connected to the containing signature over time. If a parameter is added, removed, reordered, or changed from value to The intended benefit of static void Forward(int x, string y, CancellationToken cancellationToken)
{
Target(...);
}instead of duplicating the parameter list: static void Forward(int x, string y, CancellationToken cancellationToken)
{
Target(x, y, cancellationToken);
}That said, I agree the readability tradeoff is important. One possible constraint for the feature would be to limit it to simple forwarding wrappers where the containing parameters are nearby and visible, and to disallow multiple I’m interested in whether the maintenance benefit is enough to justify that tradeoff, or whether the community feels explicit expansion should always be preferred. |
|
In some cases, a |
|
That makes sense. A The cases I’m thinking about are a bit different: forwarding layers where the parameters already belong to an existing API shape, and introducing a new wrapper type would either change the public API or add an extra abstraction only to avoid repeating the forwarding call. For example: static void Forward(int id, string name, CancellationToken cancellationToken)
{
Target(id, name, cancellationToken);
}could become: readonly record struct RequestArgs(int Id, string Name, CancellationToken CancellationToken);
static void Forward(RequestArgs args)
{
Target(args);
}but that changes both the method shape and the target shape. That may be a good design when So I agree parameter objects are a useful option in some cases. I’m mostly trying to explore whether there is value in a lighter-weight forwarding construct for wrappers/adapters/decorators where keeping the existing parameter list is intentional. |
|
The syntax looks quite confusing to me as by seeing Instead of using a btw I personally prefer void Foo(int x, int y)
{
Bar(...params);
// alternatively Bar(params...);
} |
|
If I can suggest a potential inversion of flow use case. Aware this carries a fundamental issue of loss of control over your API surface as the base type could be elsewhere. With analyzers, IDE rename, and AI (reviews and coding) it maybe isn't as much an issue as it used to be. Just thought I would throw this example as a potential use case, whether it adds or detracts from the argument. |
|
Thanks everyone for the feedback so far. I've had some time to think through the points raised here, particularly around readability, whether the syntax should be more explicit than a bare I still have my Roslyn prototype branch with the implementation and tests described in the original post, and I'd be happy to continue developing it. I'm not sure what the appropriate next step is from here, though. Is this something that would benefit from being developed into a more formal C# language proposal/specification, or should I wait for additional feedback or interest before taking it further? I also don't want to submit an implementation PR prematurely if the language-design direction hasn't reached that stage. Any guidance on the appropriate next step—or whether this is unlikely to move forward as a language feature—would be appreciated. |
|
Thanks for the additional perspective. That's a good point regarding
signature drift and the advantage a record struct can provide when the
parameter set represents a cohesive piece of data.
I think the distinction I'm still considering is between modeling
parameters as data and forwarding an existing method's arguments as part of
delegation. In the latter case, introducing a new type can change the API
design simply to reduce the repetition involved in forwarding arguments.
That said, your point about a record struct making changes propagate
structurally rather than producing compilation errors across forwarding
methods is a useful counterpoint, and I'll keep it in mind as I continue
thinking about the motivation and tradeoffs for the proposal.
Thanks for taking the time to share the feedback.
…On Sun, Aug 30, 2026 at 10:51 AM Mr. TA ***@***.***> wrote:
WRT signature drift, it's actually better to have record struct because
then you know that a change to that record "automatically" propagates to
all your methods. Whereas with listing the parameters out every time, if
one of the methods gets a parameter list change (of any sort), suddenly you
get a whole bunch of compilation errors, and you then need to go around and
sprinkle that change to all the methods.
—
Reply to this email directly, view it on GitHub
<#10308?email_source=notifications&email_token=AIBSSJ27WJTLIW7YXU5GXE35MQ5NVA5CNFSNUABIM5UWIORPF5TWS5BNNB2WEL2ENFZWG5LTONUW63SDN5WW2ZLOOQXTCOBSGA3TSMBRUZZGKYLTN5XKMYLVORUG64VFMV3GK3TUVRTG633UMVZF6Y3MNFRWW#discussioncomment-18207901>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AIBSSJYEJKAY53R3B6UYUHD5MQ5NVAVCNFSNUABHKJSXA33TNF2G64TZHM3TKOJZGA4DQOB3IRUXGY3VONZWS33OHMYTANRSGY3DSOFBOYBA>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
Uh oh!
There was an error while loading. Please reload this page.
Proposal discussion: argument forwarding expression using
...Summary
I would like to discuss a C# argument forwarding expression using
...inside invocation argument lists.The goal is to make forwarding methods, local functions, and lambdas easier to write and safer
to maintain by allowing the current callable’s parameters to be forwarded into another invocation.
The intent is that
Target(...)behaves as though the parameters ofForwardwere written explicitly:Motivation
Forwarding methods are common in wrapper APIs, overload funnels, logging/decorator layers, adapter methods, local functions, and lambdas.
Today, forwarding requires manually repeating every parameter:
This repetition is easy to get wrong when parameters are added, removed, reordered, or changed to
ref,out, orin.An argument forwarding expression would let the developer express intent directly:
Proposed behavior
For an initial version,
...would only be valid inside a normal invocation argument list.When used,
...expands to the parameters of the containing callable in declaration order.would behave like:
The forwarded arguments should participate in normal overload resolution and type inference.
Ref kind behavior
The forwarding expression should preserve parameter passing mode.
This should behave like:
Invalid cases
For a conservative first version, these should be invalid:
Open design questions
...forward all containing parameters, or only “remaining” parameters based on position?...be allowed?...be allowed in constructors or object creation?...expressions be disallowed, as proposed here?...the right syntax?Prototype
I have a local Roslyn prototype that implements this experimentally.
The prototype includes:
DotDotDotTokenForwardArgumentsExpressionSyntax...inside argument listsref,out, andinparamsref,out, andinI am not submitting the implementation as a PR yet because I understand substantial language features should be discussed first. I would like feedback on whether this direction is worth pursuing, whether the syntax/semantics are appropriate, and what design constraints should be considered before any implementation PR.
Thanks for taking the time to consider this idea. I would appreciate any feedback on whether this direction is worth exploring further.
All reactions