Description
Extend with expression to anonymous type
- Proposed
- Prototype: Done.
- Implementation: Done,
- Specification: Not Started
Speclet: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/record-structs.md
Summary
The with
expression, introduced in C# 9, is designed to produce a copy of the receiver expression, in a "non-destructive mutation" manner.
This proposal extend with
expression to anonymous type, since they are also immutable, the feature may fit well with them too.
Note: F# has a very similar feature called copy and update record expressions.
Motivation
Reduce boilerplate code to create new instances of anonymous type based on already existing instance.
Current approach:
var person = new { FirstName = "Scott", LastName = "Hunter", Age = 25 };
var otherPerson = new { person.FirstName, LastName = "Hanselman", person.Age };
Proposed:
var person = new { FirstName = "Scott", LastName = "Hunter", Age = 25 };
var otherPerson = person with { LastName = "Hanselman" };
Detailed design
The syntax is the same described in with expression section of the record proposal, that is
with_expression
: switch_expression
| switch_expression 'with' '{' member_initializer_list? '}'
;
member_initializer_list
: member_initializer (',' member_initializer)*
;
member_initializer
: identifier '=' expression
;
In the context of this proposal, the receiver expression must be an anonymous object. Also, different of the original with
proposal, the anonymous type will not need contains an accessible "clone" method, since the copy can be done by the compiler just calling the constructor of the anonymous type, what maintain how anonymous types are emitted.
Currently, each anonymous type's property has a correspondent parameter in the type constructor, with same name and type. So compiler must pass the correspondent member expression for each argument. Case the member exists in the member_initializer_list
, compiler must pass as equivalent argument the expression on the right side of the member_initializer
.
The orders each member_initializer
appears is irrelevant, since they will be processed in constructor call, therefore in the order of the parameters.
Drawbacks
None.
Alternatives
One workaround is use reflection and expression trees to build such anonymous type's constructor call. However theses features have performance cost and are, perhaps, not common for all programmers of the language. It also requires new intermediary anonymous instances to represent the properties and values that we will be changed. Here is a gist with my workaround with follow usage:
var otherPerson = person.With(new { LastName = "Hanselman" });