[Proposal] Factory Initializers #10207
Unanswered
codemonkeychris
asked this question in
Language Ideas
Replies: 1 comment
-
|
FYI regarding children, in case you haven't seen it :) |
Beta Was this translation helpful? Give feedback.
0 replies
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.
-
Resurrecting old design thread (apologies if I should just comment there): #6602
A motivating scenario, MVU/reactive programming models like: https://github.com/microsoft/microsoft-ui-reactor
Summary
Allow object/collection initializer syntax on the result of a method call,
not just after
new. A library author opts a method into this by markingit with a new
factorymodifier; callers can then writeand the trailing
{ … }behaves exactly like the initializer block ofnew T(args) { … }— init-only andrequiredmembers are assignableinside the block, and the returned instance is sealed afterwards.
Motivation
Idiomatic C# code that consumes functional-UI libraries (React-like MVU frameworks, call-site-heavy builder APIs in general) is a
deeply-nested expression that builds a tree of immutable records via
factory calls. Today call-sites are stuck in one of two unsatisfying
shapes: fluent chains that explode for anything past 4 settings (and
require 1400+ lines of per-property extension methods on the library
side), or
new T { … }object-initializerblocks that lose the factory's positional-parameter ergonomics and
sprinkle
newthrough every leaf of a deeply-nested tree.Core shape
Parens are optional when the call resolves against a zero-arg overload:
Some initial questions I hit...
factorymodifier as the gate. Trailing-initializer syntax islegal only when the called method (or local function) is marked
factory. Preserves immutability by default; library authors opt in.Alternative: no gate — any method call. We rejected this because
it would let callers mutate instances the library intended to be
shared.
Honor-system
factoryin v1. The compiler does not verifythat a
factorymethod returns a freshly-constructed instance. Weintend to follow up with a verification feature that restricts
returnin afactorybody tonew T(...)or a call to anotherfactory. Is the honor-system v1 acceptable as a staging step?In-place mutation, not copy. The trailing block is a continuation
of construction on the returned instance — same semantics as
new T { … }. We deliberately did not desugar toFactory() with { … }, because that would make every{ … }afull record copy and would conflict with non-record return types.
Parens-optional form
Name { … }. Accepts only{ <ident> = … }lookahead; empty{ }and single-element collectioninitializers (
{ x },{ x, … }) require explicit(). Tighter thanthe original spec; the permissive form caused error-recovery
regressions in existing parser tests during prototyping.
Children rule:
Children = [...], no mixed form. We deliberatelyexcluded
VStack { child1, child2 }(bare children withoutChildren =). It's the single biggest further ergonomic win for theReactor scenarios, but the design is orthogonal: "implicit property
name" is a follow-up feature that stacks cleanly on top of this one,
and keeps v1 scoped to the receiver-shape change.
Metadata. New compiler-recognized attribute
(
System.Runtime.CompilerServices.FactoryAttribute), embeddedfollowing the
IsExternalInitprecedent. Is this the right model?IOperation. Reuse
IInvocationOperation; the initializer isreachable via
ChildOperations. No new public operation kind.Analyzers that pattern-match on
IObjectCreationOperationareuntouched.
Open items
Explicit-paren
{ }lookahead is still permissive (accepts empty{ }and single-element collection shapes). That's caused 6net-new parser-test regressions in error-recovery paths where
malformed constructs ending in
(…) { }now bind as an invocation-with-empty-initializer. Two reasonable resolutions — tighten
explicit-paren to match the strict parens-optional rule, or update
the affected tests. Want LDM signal before picking.
Attribute name.
FactoryAttributeis short and matches commonEnglish usage;
FactoryMethodAttributeis more explicit. Mild lean:FactoryAttribute.factoryon extension methods, abstract/virtual methods,interfaces. All supported in the prototype and tested. Any LDM
concerns?
Expression trees. Trailing initializer inside an expression tree
is a diagnostic, not a supported form. Same stance as
withexpressions today.
What's been validated in the prototype
With a lot of AI help, I built a prototype compiler and ran against a function creation sample with
seven scenarios and had 74 tests across parsing / semantic / emit /
IOperation / classification suites, all green.
Beta Was this translation helpful? Give feedback.
All reactions