Let's say we have the following immutable records (I'm using the latest discussed record and wither syntax here, [ReadOnly]
is a :spaghetti: attribute to show that the records are actually immutable):
```c#
[ReadOnly] public class Order(Customer customer, ImmutableMap
[ReadOnly] public class OrderLine(Product product, Quantity quantity);
[ReadOnly] public class Quantity(uint value);
e.g.:
```c#
var expr = new Order(
customer,
{
[boots] = new OrderLine(boots, 100),
[gloves] = new OrderLine(gloves, 200)
});
If we need to update the innermost element, even with some kind of a wither this looks like this:
```c#
public static Order UpdateQuantity(Order order, Product product, Quantity newQuantity)
=> order with {
Lines = order.Lines.SetItem(
product,
order.Lines[product] with {
Quantity = new Quantity
}
)
};
public static Order SwitchProduct(Order order, Product oldProduct, Product newProduct)
=> order with {
Lines = order.Lines.SetItem(
newProduct,
(order.Lines?[newProduct] ?? new OrderLine(newProduct, 0)) with {
Quantity =
order.Lines?[newProduct].Quantity ?? 0 +
order.Lines[oldProduct].Quantity
}
),
Lines = order.Lines.Remove(oldProduct)
};
The withers should be nestable, like this:
```c#
public static Order UpdateQuantity(Order order, Product product, Quantity newQuantity)
=> order with { Lines[product].Quantity = newQuantity };
public static Order SwitchProduct(Order order, Product oldProduct, Product newProduct)
=> order with {
Lines[newProduct] =
order.Lines?[newProduct] ?? new OrderLine(newProduct, 0),
Lines[newProduct].Quantity =
order.Lines[newProduct].Quantity +
order.Lines[oldProduct].Quantity,
Lines = order.Lines.Remove(oldProduct)
};
The hardest part here is the indexer. Somehow most immutable collections should be able to recognize they are being used in a wither. Perhaps immutable property setters are needed for this to work transparently.
This has been brought up before and I agree that it should be possible, though I'd prefer the the already proposed syntax in the spec draft,
var p = person with
{
Name.FirstName = "NewFirstName"
};
// equivalent to
var p = person with
{
Name with { FirstName = "NewFirstName" }
};
This probably addresses the open issue regarding wither syntax more strongly,
Open issue: Does this syntactic sugar actually pay for itself?
Since in the basic form it is just the same method invocation with different tokens as stated,
A with_expression of the form
e1 with { identifier = e2, ... }
is treated as an invocation of the form
e1.With(identifier2: e2, ...)
Same analogy could be applied to property patterns as well,
if (person is { Name.FirstName: "Foo" })
// equivalent to
if (person is { Name: { FirstName: "Foo" } })
Above examples omitted the static types as they are known at the compile-time (see #11744).
@alrz I've updated the syntax to match the currently proposed one, even though it has the dreadful })
, which I cannot force myself to write together despite never placing a )
on a line by itself otherwise.
Good idea about property patterns, I think this is much easier to implement since it's a straightforward transformation.
Discussed in https://github.com/dotnet/csharplang/issues/77
Most helpful comment
This has been brought up before and I agree that it should be possible, though I'd prefer the the already proposed syntax in the spec draft,
This probably addresses the open issue regarding wither syntax more strongly,
Since in the basic form it is just the same method invocation with different tokens as stated,
Same analogy could be applied to property patterns as well,
Above examples omitted the static types as they are known at the compile-time (see #11744).