Target-typed new
expressions
Note
This article is a feature specification. The specification serves as the design document for the feature. It includes proposed specification changes, along with information needed during the design and development of the feature. These articles are published until the proposed spec changes are finalized and incorporated in the current ECMA specification.
There may be some discrepancies between the feature specification and the completed implementation. Those differences are captured in the pertinent language design meeting (LDM) notes.
You can learn more about the process for adopting feature speclets into the C# language standard in the article on the specifications.
Summary
Do not require type specification for constructors when the type is known.
Motivation
Allow field initialization without duplicating the type.
Dictionary<string, List<int>> field = new() {
{ "item1", new() { 1, 2, 3 } }
};
Allow omitting the type when it can be inferred from usage.
XmlReader.Create(reader, new() { IgnoreWhitespace = true });
Instantiate an object without spelling out the type.
private readonly static object s_syncObj = new();
Specification
A new syntactic form, target_typed_new of the object_creation_expression is accepted in which the type is optional.
object_creation_expression
: 'new' type '(' argument_list? ')' object_or_collection_initializer?
| 'new' type object_or_collection_initializer
| target_typed_new
;
target_typed_new
: 'new' '(' argument_list? ')' object_or_collection_initializer?
;
A target_typed_new expression does not have a type. However, there is a new object creation conversion that is an implicit conversion from expression, that exists from a target_typed_new to every type.
Given a target type T
, the type T0
is T
's underlying type if T
is an instance of System.Nullable
. Otherwise T0
is T
. The meaning of a target_typed_new expression that is converted to the type T
is the same as the meaning of a corresponding object_creation_expression that specifies T0
as the type.
It is a compile-time error if a target_typed_new is used as an operand of a unary or binary operator, or if it is used where it is not subject to an object creation conversion.
Open Issue: should we allow delegates and tuples as the target-type?
The above rules include delegates (a reference type) and tuples (a struct type). Although both types are constructible, if the type is inferable, an anonymous function or a tuple literal can already be used.
(int a, int b) t = new(1, 2); // "new" is redundant
Action a = new(() => {}); // "new" is redundant
(int a, int b) t = new(); // OK; same as (0, 0)
Action a = new(); // no constructor found
Miscellaneous
The following are consequences of the specification:
throw new()
is allowed (the target type isSystem.Exception
)- Target-typed
new
is not allowed with binary operators. - It is disallowed when there is no type to target: unary operators, collection of a
foreach
, in ausing
, in a deconstruction, in anawait
expression, as an anonymous type property (new { Prop = new() }
), in alock
statement, in asizeof
, in afixed
statement, in a member access (new().field
), in a dynamically dispatched operation (someDynamic.Method(new())
), in a LINQ query, as the operand of theis
operator, as the left operand of the??
operator, ... - It is also disallowed as a
ref
. - The following kinds of types are not permitted as targets of the conversion
- Enum types:
new()
will work (asnew Enum()
works to give the default value), butnew(1)
will not work as enum types do not have a constructor. - Interface types: This would work the same as the corresponding creation expression for COM types.
- Array types: arrays need a special syntax to provide the length.
- dynamic: we don't allow
new dynamic()
, so we don't allownew()
withdynamic
as a target type. - tuples: These have the same meaning as an object creation using the underlying type.
- All the other types that are not permitted in the object_creation_expression are excluded as well, for instance, pointer types.
- Enum types:
Drawbacks
There were some concerns with target-typed new
creating new categories of breaking changes, but we already have that with null
and default
, and that has not been a significant problem.
Alternatives
Most of complaints about types being too long to duplicate in field initialization is about type arguments not the type itself, we could infer only type arguments like new Dictionary(...)
(or similar) and infer type arguments locally from arguments or the collection initializer.
Questions
- Should we forbid usages in expression trees? (no)
- How the feature interacts with
dynamic
arguments? (no special treatment) - How IntelliSense should work with
new()
? (only when there is a single target-type)