csharplang
csharplang copied to clipboard
Champion "pattern-based `with` expressions"
- [ ] Proposal added
- [ ] Discussed in LDM
- [ ] Decision in LDM
- [ ] Finalized (done, rejected, inactive)
- [ ] Spec'ed
In C# 9.0, we did with on records only, not some generalized pattern-based with expression.
See also
- the proposal in proposals/records.md for one possible approach.
- Discussion of nested use cases in https://github.com/dotnet/csharplang/issues/77
Design meetings
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-09-26.md#construction-improvements
Why is it tentatively planned for 7.2 if records themselves are in 8.0?
Records build on this, not vice versa.
@MadsTorgersen are you working on a proposal for this?
pattern-based
I assume it's not only for records (where we need a new instance). For regular objects I think this should also work:
e with { id = v }
->
(var _t = e; _t.id = v; _t)
We have the same situation as switch expression where we possibly want to use with as an statement.
@alrz,
Not sure if I'm misunderstanding your example, but if e is a class instance, then surely your wither will modify the original instance, rather than creating a modified copy?
@DavidArno
Yes, I think in the absence of a With method we could just modify the original instance, see https://github.com/dotnet/csharplang/issues/1879 for some examples where this is useful. In that case, with returns the same object on the left-hand-side, so you could return it or use it an statement.
db.Persons.Find(id) with { Name = data.Name, ... };
or simply
db.Persons.Find(id) with { data.Name, ... };
I think there is a need for null checked withers here which could be implemented via with? along with await? and foreach?.
@alrz this will change with from a purely syntactic transformation like LINQ query expressions to a more involved one.
@alrz, I'd see update blocks as unrelated to this. Having a with expression modify the original data would be really bad in my view.
I think like @DavidArno We should stay with the immutable state of the with keyword.
About the with? suggestion, it looks interesting but I think it is not that useful if we have the strict null-check feature in C#8.
I agree with @DavidArno It would be confusing if sometimes with changes the original object, and sometimes returns a new one. It would be useful to have two different syntaxes.
@orthoxerox
this will change with from a purely syntactic transformation like LINQ query expressions to a more involved one.
Actually the pattern-based with is not that simple since we first need caller-receiver default-argument (mentioned in the records proposal) to substitute absent init values,
class Person {
// ...
public Person With(string name = this.Name, int age = this.Age) { ... }
}
So that something like person with { Name = "newName" } could be translated to:
(var _t = person; _t.With(name: "newName", _t.Age))
There is a lot more moving parts here but the simple initialization does not necessarily depend on it.
@DavidArno
Having a with expression modify the original data would be really bad in my view.
This is a known papercut that could simplify various applications and there's no alternatives for that either except for member-wise assignments.
@Odonno
We should stay with the immutable state of the with keyword.
The immutable state is not a property of With methods, so you could totally get around it as it's all user code, except if the class itself is defined as a "record".
I think it is not that useful if we have the strict null-check feature in C#8.
with? does not depend on NRT but it does affect it. We just produce a null check before member initialization and omit it in case of a null in LHS, much like the ?. operator.
@YairHalberstadt
It would be useful to have two different syntaxes.
I don't know what it buys you since there's no guarantee with pattern-based With methods, again, except if the class itself is defined as a "record".
@alrz That's fair enough. I agree with you proposal in that case.
@alrz
Actually the pattern-based with is not that simple since we first need caller-receiver default-argument (mentioned in the records proposal) to substitute absent init values
But this will get taken care of by the great and powerful overload resolution like any other optional arguments, won't it?
I would very much like to see this added to a future language version to accomplish my example here regarding using the with statement when implementing methods with generics.
#7752 - another case when this will be very convenient. (I don't force you to do this, it's all up to you)