dotnet / dotnet/csharplang

[Proposal] Extend with expression to anonymous type

Open
#3,530 31 comments 63 reactions 1 assignee Claimed by @333fred View on GitHub
Implemented Needs ECMA Spec Proposal champion Smallish Feature
Dominant language
C#
Stars
12.7k
Forks
1.1k
Avg merge
11h 1m
Merged PRs (30d)
3

Description

# Extend with expression to anonymous type

* [x] Proposed
* [x] Prototype: Done.
* [x] Implementation: Done,
* [ ] Specification: Not Started

Speclet: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/record-structs.md

## Summary
[summary]: #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](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/copy-and-update-record-expressions).

## Motivation
[motivation]: #motivation

Reduce boilerplate code to create new instances of anonymous type based on already existing instance.

Current approach:

```c#
var person = new { FirstName = "Scott", LastName = "Hunter", Age = 25 };

var otherPerson = new { person.FirstName, LastName = "Hanselman", person.Age };
```
Proposed:

```c#
var person = new { FirstName = "Scott", LastName = "Hunter", Age = 25 };

var otherPerson = person with { LastName = "Hanselman" };
```

## Detailed design
[design]: #detailed-design

The syntax is the same described in [with expression section of the record proposal](https://github.com/dotnet/csharplang/blob/master/proposals/records.md#with-expression), that is

```antlr
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
[drawbacks]: #drawbacks

None.

## Alternatives
[alternatives]: #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](https://gist.github.com/leandromoh/d81d8ad17f34d10c5b5d0ed5ac13fe87) with follow usage:

```c#
var otherPerson = person.With(new { LastName = "Hanselman" });
```

## Unresolved questions
[unresolved]: #unresolved-questions

## Design meetings
- https://github.com/dotnet/csharplang/blob/main/meetings/2020/LDM-2020-06-22.md#with-expressions-on-non-records (approved this scenario)

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.