Null-conditional assignment #8676
Replies: 30 comments
-
i would not do this :) Let's be consistent with the way we already do conditionals today. On the IDE, we'll just need to update some of our conditional helpers we have today, and we'll have to audit all code that handles assignments. Seems doable. |
Beta Was this translation helpful? Give feedback.
-
I'd expect M(a?.b?.c = d); be equivalent to: M(a is null
? d
: (a.b is null
? d
: (a.b.c = d)); so that for any expression, be it
|
Beta Was this translation helpful? Give feedback.
-
I would expect that if 'a' was null that no further work was done. That's the prime use case as otherwise we'll execute expensive work that will be thrown away. Having this work differently fro an argument vs an expression-statement would just be enormously weird at that point. If someone ends up writing I would also be entirely ok with the expression form of this being disallowed, and only allowing the statement form. |
Beta Was this translation helpful? Give feedback.
-
If we were to adopt the semantics you described @quinmars, then in
|
Beta Was this translation helpful? Give feedback.
-
Nevermind, you are both correct. The lazy evaluation is a very important part of the feature and shouldn't be sacrified for my first impression when seeing this code. I find the symmetry argument ( |
Beta Was this translation helpful? Give feedback.
-
This has consequences for lambda return type inference, so it would leave a weird back-compat wart if this restriction was shipped and then loosened later. |
Beta Was this translation helpful? Give feedback.
-
Let me rephrase: i would prefer to disallow one of the forms over having the two forms have different semantics :) If hte two forms have the same semantics, then there's no problem and i wouldn't disallow either of them. |
Beta Was this translation helpful? Give feedback.
-
I agree, having the expression mean something different when directly a statement would be frightening. I also initially expected |
Beta Was this translation helpful? Give feedback.
-
Awesome to see this moving forward! 🎉 Just wanted to bubble up our use case from the Windows Community Toolkit where we have to do event hooks all the time (from this original comment here):
if (_tabItemsPresenter != null)
{
_tabItemsPresenter.SizeChanged -= TabView_SizeChanged;
}
if (_tabScroller != null)
{
_tabScroller.Loaded -= ScrollViewer_Loaded;
}
_tabContentPresenter = GetTemplateChild(TabContentPresenterName) as ContentPresenter;
_tabViewContainer = GetTemplateChild(TabViewContainerName) as Grid;
_tabItemsPresenter = GetTemplateChild(TabsItemsPresenterName) as ItemsPresenter;
_tabScroller = GetTemplateChild(TabsScrollViewerName) as ScrollViewer;
if (_tabItemsPresenter != null)
{
_tabItemsPresenter.SizeChanged += TabView_SizeChanged;
}
if (_tabScroller != null)
{
_tabScroller.Loaded += ScrollViewer_Loaded;
}
_tabItemsPresenter?.SizeChanged -= TabView_SizeChanged;
_tabScroller?.Loaded -= ScrollViewer_Loaded;
_tabContentPresenter = GetTemplateChild(TabContentPresenterName) as ContentPresenter;
_tabViewContainer = GetTemplateChild(TabViewContainerName) as Grid;
_tabItemsPresenter = GetTemplateChild(TabsItemsPresenterName) as ItemsPresenter;
_tabScroller = GetTemplateChild(TabsScrollViewerName) as ScrollViewer;
_tabItemsPresenter?.SizeChanged += TabView_SizeChanged;
_tabScroller?.Loaded += ScrollViewer_Loaded; |
Beta Was this translation helpful? Give feedback.
-
Question concerning the For structs it also feels a bit weird to see Reason I ask is that it is easy enough to get race conditions with this, especially with UI or those that handle parallelism and it can easily assign incorrectly in cases when not aware. |
Beta Was this translation helpful? Give feedback.
-
As the OP says:
|
Beta Was this translation helpful? Give feedback.
-
My bad, the repeated parts later in example on put on wrong footing, as it feels far from equivalent. |
Beta Was this translation helpful? Give feedback.
-
@RikkiGibson for thoughts on nullable structs. |
Beta Was this translation helpful? Give feedback.
-
I think we probably have to block assignments to members of nullable structs for the same reason we block the following: SharpLab public struct S
{
public string Prop { get; set; }
}
public class C {
public void M(S? s) {
s.Value.Prop = "a"; // error CS1612: Cannot modify the return value of 'S?.Value' because it is not a variable
}
}
|
Beta Was this translation helpful? Give feedback.
-
Just as well that's easy enough to include. A simple check to see if you're dealing with a Nullable<> and the condition evaluates to: s?.Prop = "a"; // equivalent to:
if (s is not Nullable) // pseudo code
{
// Do nullable reference type check
}
else if (s.HasValue)
{
s = s.Value with { Prop = "a" };
} Of course the real question being "should it be included"? I would say either "yes" or "not completely no"... the with expression makes this tricky. With solves the problem of modifying record and (as of c#10) structure types: cool, we can assign stuff in a single block now. Then, what about this? S? ns;
S s;
s = ns with { Prop = "a" }; // doesn't compile, should be:
if (ns.HasValue)
{
s = ns.Value with { Prop = "a" };
} But that sucks! I want the ?. operator to handle it! So then it should be: s = ns?.Value with { Prop = "a" }; // won't execute if null. But if we implement that, we might as well add s?.Prop = "a"; // evaluates to:
s = s?.Value with { Prop: "a" }; // evaluates to:
if (s.HasValue)
{
s = s.Value with { Prop: "a" };
} Well great, we're there. Or are we? Just to throw one more wrinkle in the mix, what happens if we do this? S? ns;
S? ns2 = new S();
ns2 = ns?.Value with { Prop = "a" }; // Should this return null or leave ns2 unmodified? I don't know the answer, but I do like the discussion. Single line assignment cases like this aren't going away, the community will keep asking until all of them are eradicated. The same thing happened with events, which after many tantrums turned into the now beautiful standard |
Beta Was this translation helpful? Give feedback.
-
Would this syntax be legal, and would it force the right hand side to be evaluated: c?.E ?? _ = ExprWithSideEffects(); |
Beta Was this translation helpful? Give feedback.
-
I think it is not legal, because we don't define I think if you wanted semantics like "always do this, and assign it to some target if present" we would want you to do something like var result = ExprWithSideEffects();
c?.E = result; |
Beta Was this translation helpful? Give feedback.
-
Regarding #6045 (comment) Thanks for the very interesting discussion. It feels like nullable value types and reference types are maybe experiencing an analogous issue here. SharpLab C? Method1(C? input)
{
return input with { field = 43 }; // warning: 'input' may be null
}
S? Method2(S? input)
{
return input.Value with { field = 43 }; // warning: Nullable value type may be null.
}
record C { public int field; }
struct S { public int field; } In the original proposal, we are improving the ergonomics of "update this thing if it not null", but we are maybe leaving behind "create an updated version of this thing if it is not null". The closest thing to what we really want is still I'm not certain whether/how to address but I think we should keep an eye on it and see if people keep hitting it. |
Beta Was this translation helpful? Give feedback.
-
A discussion of |
Beta Was this translation helpful? Give feedback.
-
“The closest thing to what we really want is still Considering that I recognize it as its separate feature now. Thanks @jnm2 for the link, I’ve followed up! |
Beta Was this translation helpful? Give feedback.
-
Inspired by the ECMA committee discussion on dotnet/csharpstandard#664 As we do this, we need to define what happens in a case like this: (a?.B, c?.D) = (e.M1(), f.M2()); Whatever we do, we should be consistent with the current evaluation order for tuple / deconstruction assignment: (x, y) = (y, x); // swap I'll suggest that the order of evaluation (non-spec language) be:
/cc @MadsTorgersen |
Beta Was this translation helpful? Give feedback.
-
afaict, this is not null-conditional-assignment. It doesn't match teh grammar for that feature. Specifically, the grammar is: null_conditional_assignment
: null_conditional_member_access assignment_operator expression
: null_conditional_element_access assignment_operator expression Neither of which match the above. As such, this is normal assignment. As such, the LHS doesn't produce LValues to assign into, so it's simply illegal and doesn't compile. |
Beta Was this translation helpful? Give feedback.
-
If you have specific syntax proposals that you would like to suggest based on Swift feel free to open new discussions. Swift does treat optionals differently than C#, though, in the sense that you are more or less forced to unwrap them into a different identifier. You don't have to do that in C#, and the idiomatic null check |
Beta Was this translation helpful? Give feedback.
-
I'm suggesting that you represent the change that you would like to see, open discussions around features or syntax that you would like to see C# implement. Guidance like "be like Swift" isn't really actionable, but specific examples of syntax that you think would make development simpler could be considered. |
Beta Was this translation helpful? Give feedback.
-
The proposal should clarify this circumstance, if ((objName?.a = func()) == null)
{
} In which null-aware assignments in an if statement condition will return:
Assignments still must follow the code of:
Its important to keep the idea that null should be emplaced where if (null == null)
{
} Actually, it was pointed out that this is defined in the proposal, However, I think a welcome change is to add code too in addition to grammar. |
Beta Was this translation helpful? Give feedback.
-
To me this looks implied from the semantics that the expression has when it's not an expression statement; meaning the expression's value is used. The same would apply to all contexts, including boolean variable assignments, if statements, while statements, do-while statements, for statements, etc. The semantics are pretty clear for this case, even though it's probably best to never write code like that and split out the assignment's result into a variable and then compare it to null. |
Beta Was this translation helpful? Give feedback.
-
This is gold, can't tell you guys how many times I've written assign foo to bar if bar isn't null with ?. and had to remember it's not possible. |
Beta Was this translation helpful? Give feedback.
-
Are there any chances this is being included in C# 13? We've been waiting for this improvement for quite a long time and it's a massive QoL. Last time this was touched by the LDM was in 2022. |
Beta Was this translation helpful? Give feedback.
-
Very unlikely at this point. |
Beta Was this translation helpful? Give feedback.
-
We have started impl work dotnet/roslyn#75821 |
Beta Was this translation helpful? Give feedback.
-
Summary
Permits assignment to occur conditionally within a
a?.b
ora?[b]
expression.The full proposal for this feature has moved to https://github.com/dotnet/csharplang/blob/main/proposals/null-conditional-assignment.md.
Design meetings
Beta Was this translation helpful? Give feedback.
All reactions