Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Champion "Null-coalescing assignments" (16.3, Core 3) #34

Open
2 of 5 tasks
MadsTorgersen opened this issue Feb 9, 2017 · 100 comments
Open
2 of 5 tasks

Champion "Null-coalescing assignments" (16.3, Core 3) #34

MadsTorgersen opened this issue Feb 9, 2017 · 100 comments
Assignees
Labels
Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification Proposal champion Smallish Feature
Milestone

Comments

@MadsTorgersen
Copy link
Contributor

MadsTorgersen commented Feb 9, 2017

Introduces ??=.

Note: a minor change to ?? is included in this feature as implemented, so that it allows unconstrained type parameter types on the left.

LDM history:

@MgSam
Copy link

MgSam commented Feb 10, 2017

Glad to see this feature graduated from the "we will never do this!" bucket. :)

@Thaina
Copy link

Thaina commented Feb 17, 2017

Should we also support &&= and ||= operators?

Yes please

@devinvisible
Copy link

Very excited for this! Nice syntactic sugar similar to Ruby's ||=

@paulomorgado
Copy link

@Thaina, what are the use cases for &&= and ||=.

@Thaina
Copy link

Thaina commented Mar 16, 2017

@paulomorgado

var mustAllTrue = Condition0;
mustAllTrue &&= Condition1;
mustAllTrue &&= Condition2;

@CyrusNajmabadi
Copy link
Member

@Thaina You can already do that today with:

var mustAllTrue = Condition0;
mustAllTrue &= Condition1;
mustAllTrue &= Condition2;

@yaakov-h
Copy link
Member

@CyrusNajmabadi Wouldn't that still unconditionally evaluate the right-hand side? I'd expect in a situation such as:

var val = true;
val &&= BigExpensiveOperationReturningBool();

that BigExpensiveOperationReturningBool is not executed.

@CyrusNajmabadi
Copy link
Member

@CyrusNajmabadi Wouldn't that still unconditionally evaluate the right-hand side?

Ah, point taken.

@paulomorgado
Copy link

if val is true, that means that it will end up with whatever BigExpensiveOperationReturningBool() returns, right?

Short circuiting would only happen for:

var v1 = false;
v1 &&= BigExpensiveOperationReturningBool();

var v2 = true;
v1 ||= BigExpensiveOperationReturningBool();

I'll agree that move statements to expressions can be more expressive.

@yaakov-h
Copy link
Member

@paulomorgado true. 😅

@Spongman
Copy link

Spongman commented Mar 22, 2017

@MadsTorgersen very glad to see you finally changed your mind on this "contrived" scenario ;)

https://connect.microsoft.com/VisualStudio/Feedback/Details/585580

@MgSam
Copy link

MgSam commented Mar 22, 2017

@Spongman To be fair, Mads said in that thread he thought ??= might be worthwhile, but ||= and &&= were probably not given the finite budget for features. I agree with him on that.

@alrz
Copy link
Member

alrz commented Mar 23, 2017

dotnet/roslyn#13951 and dotnet/roslyn#1276 are also related here.

obj?.Property = value;
obj?.Event += handler;

@ashmind
Copy link

ashmind commented Apr 4, 2017

Note that I did a prototype for this one a while ago, at ashmind/roslyn@cf5ee99.

It was available on TryRoslyn for a while, but fell too far behind main roslyn branch at the moment -- I'll see if I can merge with latest.

@alrz
Copy link
Member

alrz commented Jun 1, 2017

This proposal would allow to null check an expression without assigning it (if permitted),

arg ??= throw new ArgumentNullException(nameof(arg));
// instead of
_ = arg ?? throw new ArgumentNullException(nameof(arg));

It could also be used with return, continue and break expressions.

@ghost
Copy link

ghost commented Jun 11, 2017

@Thaina

Yes please

It is not an opinion... just text...

Yes it should be implemented but guys please consider real discussion provided here: #397

@MadsTorgersen MadsTorgersen modified the milestones: 7.X candidate, 8.0 candidate Oct 25, 2017
@Thaina
Copy link

Thaina commented Apr 4, 2019

@Shawn-Twinmaple We could do this

void DoStuff() => this.Data = this.Data ?? this.device?.ReadDataFromStream();

@jnm2
Copy link
Contributor

jnm2 commented Apr 4, 2019

@Thaina The shorthand for that will be ??=. Shawn is asking for a shorthand where this.Data is set to this.device?.ReadDataFromStream() if the latter is not null, regardless of whether this.Data was previously null.

@Thaina
Copy link

Thaina commented Apr 4, 2019

@jnm2

void DoStuff() => this.Data = this.device?.ReadDataFromStream() ?? this.Data;

?

@jnm2
Copy link
Contributor

jnm2 commented Apr 4, 2019

@Thaina Sort of, but not quite. In your example, you end up setting this.Data = this.Data if ReadDataFromStream returns null, but this can have side effects.

If you read Shawn's comment, he has the exact code you're looking for.

@Thaina
Copy link

Thaina commented Apr 4, 2019

@jnm2 I just think he just want to make it shorter to write. And so if it just a field I think it would have no side effect

If we need to avoid side effect for property too, well, maybe we need another feature request I have seen about allow putting expression in operator

void DoStuff() => this.Data = this.device?.ReadDataFromStream() ?? return;

@jnm2
Copy link
Contributor

jnm2 commented Apr 4, 2019

@Thaina Yes, that's exactly what he (and I) have just said. He wants it to be shorter to write.

I really want that ?? return feature (it just keeps coming up for me for other reasons) but I think it's already been turned down by the LDT (link). Also, it's not general enough to solve what Shawn is asking for. Imagine three of these statements in a row. You can't use ?? return more than once per method.

@Shawn-Twinmaple
Copy link

@Shawn-Twinmaple We could do this

void DoStuff() => this.Data = this.device?.ReadDataFromStream() ?? this.Data;

Yep exactly.

That become this:
void DoStuff() => this.Data ?!= this.device?.ReadDataFromStream();
In this way, the need to re-assign to itself is simply short-circuited away.

Since both ! and != are already implemented negation operators I'm not really sure which "operator symbology" would work best for this.

Symbology aside
??= is great for null coalescing assignments, but not-null assignments are also a problem too, for the same reason.

Ideally there would be a solution both for null and not-null assignments, as they are both very common place occurrences in code found everywhere.

The proposed solution must work with nullable valuetypes and nullable reference types.
It must also work with non-nullable value types and non-nullable reference types.

I have attached some test examples that I believe covers all 4 of those cases for both proposed operators...
160 lines of code, far too much to post here.
Comments, Problems and Solutions for each case are all in the file.
test_examples.zip

I think the two operators really go hand-in-hand just like == and != does today;
but as suggested, this might need to be in its own feature request...

@yaakov-h
Copy link
Member

LDM Update: The result of the ??= expression will be the result of an implicit conversion from the RHS to the underlying type of the LHS, if one exists.

That means that #34 (comment) will happen (and hey, it looks like my exact example was used in the LDM 😄)

@jnm2
Copy link
Contributor

jnm2 commented Apr 17, 2019

@333fred and team, you're the best. Thanks for this!

@gafter gafter closed this as completed Apr 17, 2019
@PathogenDavid
Copy link

@gafter I think you hit the close+comment button by mistake.

@jcouv jcouv reopened this Apr 18, 2019
@wanton7
Copy link

wanton7 commented May 6, 2019

Does this feature mean I can finally use (list ??= new List<int>()).Add(7); instead of (list = list ?? new List<int>()).Add(7); in C# 8.0?

@yaakov-h
Copy link
Member

yaakov-h commented May 6, 2019

Yes.

@333fred
Copy link
Member

333fred commented Jun 11, 2019

@jnm2 @yaakov-h dotnet/roslyn#36329 and #2591. We're hoping that this change will be in the final version of 16.2 or some preview before then.

@yaakov-h
Copy link
Member

@333fred yay, thanks!

@ufcpp
Copy link

ufcpp commented Jun 17, 2019

Is it possible to make LHS of the null-coalescing assignments by-ref?

using System;

class Program
{
    static void Main()
    {
        string[] values = new string[1];

        // OK
        ref var r1 = ref values[0];
        r1 ??= "abc";
        Console.WriteLine(values[0]);

        // NG
        ref var r2 = ref values[0] ??= "default string";
    }
}

@CoderVision
Copy link

I love the idea of the ??= feature! I have been periodically checking updated compilers to see if it is included and was happy to see that it has a preview status.

@333fred
Copy link
Member

333fred commented Jun 26, 2019

@ufcpp no compound assignment works that way, and it would conflict with the nullable-value-type change that was just implemented.

@jcouv jcouv changed the title Champion "Null-coalescing assignments" Champion "Null-coalescing assignments" (16.3, Core 3) Jul 18, 2019
@mattwar mattwar mentioned this issue Jul 31, 2019
@jnm2
Copy link
Contributor

jnm2 commented Nov 3, 2019

@yaakov-h at #34 (comment):

LDM Update: The result of the ??= expression will be the result of an implicit conversion from the RHS to the underlying type of the LHS, if one exists.

That means that #34 (comment) will happen (and hey, it looks like my exact example was used in the LDM 😄)

@333fred This change made a real difference to me today. Thanks again!

private bool? foo;

public bool Foo => foo ??= Properties.GetDataSourceRowByKeyValue(EditValue) is null;

@jcouv jcouv assigned jcouv and unassigned jcouv May 12, 2020
@333fred 333fred added the Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification label Oct 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification Proposal champion Smallish Feature
Projects
None yet
Development

No branches or pull requests