csharplang/proposals/target-typed-new.md
2020-03-24 08:48:10 -07:00

4.8 KiB

Target-typed new expressions

  • Proposed
  • Prototype
  • Implementation
  • Specification

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();

Detailed design

The object_creation_expression syntax would be modified to make the type optional when parentheses are present. This is required to address the ambiguity with anonymous_object_creation_expression.

object_creation_expression
    : 'new' type? '(' argument_list? ')' object_or_collection_initializer?
    | 'new' type object_or_collection_initializer
    ;

A target-typed new is convertible to any type. As a result, it does not contribute to overload resolution. This is mainly to avoid unpredictable breaking changes.

The argument list and the initializer expressions will be bound after the type is determined.

The type of the expression would be inferred from the target-type which would be required to be one of the following:

  • Any struct type (including tuple types)
  • Any reference type (including delegate types)
  • Any type parameter with a constructor or a struct constraint

with the following exceptions:

  • Enum types: not all enum types contain the constant zero, so it should be desirable to use the explicit enum member.
  • Interface types: this is a niche feature and it should be preferable to explicitly mention the type.
  • Array types: arrays need a special syntax to provide the length.
  • dynamic: we don't allow new dynamic(), so we don't allow new() with dynamic as a target type.

All the other types that are not permitted in the object_creation_expression are excluded as well, for instance, pointer types.

When the target type is a nullable value type, the target-typed new will be converted to the underlying type instead of the nullable type.

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(); // ruled out by "use of struct default constructor"
Action a = new(); // no constructor found

Miscellaneous

throw new() is disallowed.

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 a using, in a deconstruction, in an await expression, as an anonymous type property (new { Prop = new() }), in a lock statement, in a sizeof, in a fixed statement, in a member access (new().field), in a dynamically dispatched operation (someDynamic.Method(new())), in a LINQ query, as the operand of the is operator, as the left operand of the ?? operator, ...

It is also disallowed as a ref.

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)

Design meetings