Compound Assignment, Part Two


Last time I discussed how the compound assignment operators of the form “x op= y” have some perhaps unobvious behaviours in C#, namely:

(1) though logically this is expanded as “x = x op y”, x is only evaluated once
(2) for built-in operators, if necessary, a cast is inserted, so that this is analyzed as “x = (T)(x op y)
(3) for built-in operators, if “x = y” would be illegal then so is “x op= y

I am pleased to announce that we are at long last filling in some holes in the C# language. We at present support compound assignment on the addition, subtraction, division, remainder, multiplication, left shift, right shift, bitwise and, bitwise or and bitwise xor operators, but that leaves out a large number of operators. In the next version of C# we will be adding compound assignment operators for most of the remaining binary (and ternary!) operators.

Doing so requires us to relax some of the rules above; I’ll describe why below.

Let’s start with an easy one.

Equality/Inequality

In the next version of C#, x !== y will mean x = (x != y).

Now, for all the built-in types, x != y returns bool, so on the built-in types this syntax only works if x and y are both bool. This was actually the easiest of the new compound operators to implement because it already was implemented! x !== y on bools is the same as x ^= y, which we already had.

Similarly, x === y is the opposite; it is the same as x = !(x^y). Or is it the same as x = (!x)^y? Turns out, both! Is that ever weird!

I am slightly worried that people conversant with JScript will confuse the meaning of === in JScript (equality without type conversion) and === in C# (compound assignment with equality) but I hope it will not be too confusing.

The compound equality and inequality operators are trivial; what about the other comparison operators? Can we make them into compound operators?

Comparison

Were there any justice in this world, x <= y ought to mean x = x < y, but unfortunately <= is already taken. Similarly with x >= y. Regrettably we do not have a compound assignment syntax for these, but it’s probably just as well, as you’ll see in the next point:

x >== y and x <== y mean x = x >= y and x = x <= y, respectively. Now, for all the built-in types upon which they are defined, <= and >= return bool, but there are no <= or >= operators defined on bool (or object) and therefore, the compound operators are useful on no built-in types. However, we will support them. If you create a user-defined type, call it C, such that C has a user-defined explicit or implicit conversion from bool, and a user-defined <= operator, then you can say x <== y for expressions x and y of type C. It expands as x = (x <= y), of course, modulo that x is computed only once.

Conditional

One more before we leave the “bool” trap. An exciting opportunity here is to extend the compound assignment operators from binary operators to ternary operators. Now, C# only has one ternary operator, the conditional operator, but in general we can treat any “infix” ternary operator as two binary operators. That is, rather than thinking of x ? y : z as a ternary operator, think of it as two binary operators, ? and :, which always appear together. Once you think of it like that then you can see how we can make this into a compound assignment operator:

x ?= y : z means x = x ? y : z

Clearly they must all be bools or types convertible to bool. In addition, we strengthen the requirement of the third rule: for built-in types both y and z must be assignable to the type of x.

A frequent question on StackOverflow is why the conditional operator does not take into account the type to which it is being converted; in this syntax, it does because again, a cast is inserted if necessary if the types are built-in types. Hopefully that will decrease user confusion at least slightly.

Coalescing

x ??= y means x = x ?? y

This one is actually pretty straightforward and very useful. The type analysis of ?? is a bit odd (see the spec for details) but the semantics of the operator already require that the left and right sides have type compatibility, at least modulo nullability. The operator basically means “if the left side is null, replace it with the right side, otherwise keep it the same and use its value”.

Thus far we’ve seen the easy ones. In the remaining operators we completely remove the restriction that “y” be assignable to “x”; the reasons will become clear.

Type comparison

x is= Y means x = x is Y

again, since “is” only returns bool, the only types this works on are bool and object. For example:

object x = “hello”;
x is= Exception;

means x = (x is Exception), so x becomes a boxed “false”. Similarly:

x as= Y means x = x as Y

This one essentially keeps x the same if it is of type Y, and turns it to null if it is not.

We relax the restriction that x = Y be legal for built-in operators in both these cases, since it practically never will be.

Call, index and member access

x ()= y means x = x(y)

This one takes some thinking. Clearly x must be of delegate type so that it can be invoked. And the delegate invoked must return a delegate assignable to x. I did an article on delegates like that a while back; essentially this operator works best on combinators:

delegate D D(D d); // D is a delegate which takes a D and returns a D.
void M()
{
    D x = q=>q;
    D y = r=>s=>r(s);
    x ()= y;
    // means x = x(y), which in this particular case, just assigns y to x.
}

Clearly in this example it is very useful that y be assignable to x, but it is not required.

Now, you might say, doesn’t this already have a meaning? That is, x() = y means assign the value of y to the variable x(). But in C#, the result of a method invocation is never a variable, it is always a value (or void).

This is not true in general in the CLR; as I noted last time, the type system supports methods that return an alias to a variable. If we ever want to add variable-returning methods to C#, this is going to be tricky. We considered not adding this compound assignment operator because it might make it more difficult to add the variable-returning-method feature in the future. But a great feature today is worth it; we might never implement the variable-returning-method feature and it seems a shame to deprive people of this awesome syntax as a result of a hypothetical future feature.

Similarly to invocation, we can do indexing. x []= y means x = x[y]. The typical case is that x is a type that contains an indexer which returns an instance of that type.

A slightly odd one that was controversial in the design meetings was member access. x .= Y means x = x.Y — clearly Y must be a field or property of a type compatible with x, or a method group such that x is a delegate type compatible with it. (That would restrict Y to be names of methods of a delegate type, like Invoke.)

Stuff we’re not supporting

That leaves the new, anonymous method and lambda operators. After much debate we decided to not support x new= Y, x delegate={y} or my personal favourite, x=>=y. Note that the latter would mean x = x=>y, which violates the rule that the same simple name not have two meanings in the same block.

We hope you enjoy these new operators; I’m hoping we can do a second preview release of the compiler soon so that you can experiment with how these operators interact with async/await!

.

.

.

.

 

UPDATE: HA HA HA HA HA HA HA HA HA HA HA HA HA HA HA! I totally crack myself up.

In case it is not clear — and based on the number of comments I got of the form “Are you serious or is this an April Fools Day joke?” it was exactly the right amount of unclear — this is a joke; we are not adding any of these operators. Part One is of course perfectly serious

One of the most common comments is that “??=” actually is useful. Indeed, it would be pretty useful, though we have no plans to do it at this time. It was a comment suggesting ??= on last year’s April Fool’s Day post that inspired this one.

Comments (54)

  1. SSS says:

    I'm not really seeing (m)any practical uses for any of this. Do you have some examples of how this might be used in practice?

  2. SWeko says:

    Is it a coincidence that this is posted on the 1st of April?

    HA HA HA HA HA HA HA HA HA HA HA HA HA HA HA HA HA HA! I crack myself up! — Eric

  3. Oliver says:

    Would it be possible to overload these operators for my class or do they fall into the group of non-overloadable operators?

  4. NB says:

    Is this an April's Fool joke?

  5. kookiz33@hotmail.com says:

    That's a joke… right?

  6. 13xforever says:

    I hope this is a hoax, because most of them are even less useful than that famous "goes to" (–>) operator.

  7. Thomas Levesque says:

    April's fool !

    That was pretty obvious after reading the section about the first new operator…

  8. JD says:

    SSS the main purpose of these operators will be to separate the wheat from the chaf during the interview process

  9. Mario says:

    Finally, the humble bool type gets some long-deserved love. While you are at it, I hope you also insert the long missing short-circuited version of the xor operator. I understand it cannot really provide short-circuiting, but at least it would make our code look more cheerful.

    ^^

  10. Mario Vernari says:

    It is not a joke!!!

    Rather I'd suggest some useful extension also. The async-compound operator, for example.

    We may write as X====Y, where that is the syntactic sugar of X===Y evaluated asynchronously.

    To be honest, there would be the parallel-async-compound too. By writing C1=====C2, we may avoid tons of code, instead of evaluating asynchronously, in parallel mode, the equality of enumerable objects.

    That will open the door to C# 6, where an huge application, will be written as:

    X=============================================Y  //to install plugins just add more =

    Amazing!… Eric you're great!

  11. Florian says:

    Eric, I am deeply interested in your work, especially your efforts geared towards simplifying the C# language.

    Could you also consider, along with the compoind assignment rewrite, adding the missing short-circuit conditional expression operator  ^^ (XOR) ? C# already defines the short-circuit && and || operators, it's a shame XOR has no love.

    Best Regards,

    Florian

  12. Florian says:

    @Mario: Great minds think alike… I would subscribe to your newsletter.

  13. Paul says:

    AprilFool ?= RealThing

  14. Bill P. Godfrey says:

    Shoudln't the compound operators also have a compound operator?

    x+==y, which would be the same as x=(x+=y), or x=(x=(x+y)) ?

    Then we'd also need x+===y.

  15. matt.weber@emergingsoft.com says:

    I will be deeply upset if this fantastic development is merely a "joke".

  16. Ch. Klauser says:

    Actually there is one operator in there that I'd actually like to see in a future C# version:  ??=

  17. pc says:

    Well played.

  18. Lucero says:

    Hm, April's fool or not? There seems to be quite some thought put into this. Yet apart from a few useful ones (especially ?= and ??=) I don'r really see the point yet, but the last sentence about "how these operators interact with async/await" just doesn't seem to fit the April joke part.

    Anyways, will it be possible to invoke methods and indexers with multiple arguments?

    x ()= y, z;

    Wow, how confusing is that?

  19. Quick Joe Smith says:

    It can safely be assumed that any blog posted on April 1st should be treated as a joke. That said, while we are on the topic of new language features, explicit parameterless constructors for structs would be nice. Case in point:

    I just went through the degrading experience of trying to fashion a struct to represent a historical date type (consisting of a year & an era, so BCE dates are possible), and was not able to come up with a way to avoid a 0 year value that didn't feel like a dirty hack.

  20. Jonathan Pryor says:

    @Lucero: Putting lots of thought into something does not mean it can't be an April Fools joke. There are lots of April Fools RFCs that would prove that, many of which have some degree of effort put into them: en.wikipedia.org/…/April_Fools&

  21. Jonathan Pryor says:

    @Quick Joe Smith: Useful as that might be, it won't happen as it would slow down array allocation, as the provision of a default struct constructor would require that the default constructor be invoked on each element within an array, which would (obviously) slow things down. If the default constructor _wasn't_ invoked in arrays, that would be a _really_ weird language "wart." This would also require CLR support, and I doubt that the CLR folks would want to add such support.

  22. Ben says:

    Arrghgh! I got half way through before Sum(WTF) = Doh!(Date).

    What makes this one particularly insidious is yesterday's non-April-1st set-up.

    Evil genius at work…

  23. FedeAzzato says:

    This really looks like an april fools day joke. I agree it would be nice to have the "??=" operator, specially if that expanded to something like:

    lock (something) {

       x = x ?? y;

    }

    But also "??=" could be used in expressions, so that would allow us to write thing like "(x ??= y).DoSomething()".

    Wait a second, think I'm seeing the Singleton Pattern over here.

  24. Joren says:

    Once again a great april fools joke, well done :)

  25. Jeff Yates says:

    Mr Lippert, you are a card. Thanks for the entertainment! :D

  26. Mark Lysaght says:

    Jaysus Eric you've far to much time on your hands mate! I was half way through last years "–>" & "++>" operators before I saw them for what they were, I'm quite proud I spotted this one immediately.

    . . . also, having been pranked twice already today I was ACUTELY aware it was April 1st!

  27. Jon Skeet says:

    It's fabulous that you went to the trouble of a (genuinely useful) "part 1" blog post as the prelude to an April fool post. (I've just tried to find a "C# starts looking like Yoda" April fool post from the past, but maybe that wasn't you…)

    On the other hand, I'm really hoping that the "second preview release of the new compiler" *isn't* a joke. It'd be really nice to be able to use async on VS2010 SP1 soon… I don't suppose you can give us any indications on that front?

  28. dmihailescu says:

    statements like x !== y look more like typos than usefull constructs.

    Eric waited until 1.00 a.m on April 1st, so I hope that's joke for C#'s sake.

  29. Bugedone says:

    Eric, didn't you pull this gag last year on April 1st?

  30. Micah says:

    I was at the part about === before I realized what day it is. Good stuff.

  31. Joe White says:

    @Fede, I disagree. ??= shouldn't have an inherent lock, any more than += should. But you can already do the locking version of ??= using the Lazy<T> type. There's a constructor parameter that lets you specify whether the Lazy should be threadsafe.

    But I, too, dearly wish that the ??= part was not an April Fools joke. I want to be able to lazy-initialize something without changing the semantics of *every* call site. Obviously you can write out "x = x ?? y", but it pains me not to have the same concise syntax as += and friends.

  32. Jon Skeet says:

    A colleague has pointed out one operator this doesn't work well with: assignment itself.

     int x = 10;

     Console.WriteLine(x == 10);

    Does that mean "compare x with 10 and call Console.WriteLine(bool)" or does it expand to:

     int x = 10;

     Console.WriteLine(x = x = 10);

  33. Lukas M says:

    What a bunch of nonsense :D

  34. oldnewthing says:

    Wha't no LINQ love? Maybe C# version N+2 can finally have "x where= z =>condition(z)".

  35. Missing the //= operator says:

    I suggest that the following assignment

    x //= y;

    simply leaves x as it is. I know it already works, but it's a shame to have to put the semicolon on the next line.

  36. Mike Murray says:

    You got me for a bit; good stuff! The ??= and as= operators seem like they could actually prove useful.

    Love the //= operator suggestion from the commenter above! How about the ;= operator? :)

  37. Mario Vernari says:

    Yaaaa!!!…That's an idea!…The //= operator!…Why not the x /*=y and the x */= y also?

    I guess that it will cost an extra effort for the C# guys.

  38. Filini says:

    The "Compound Assignment, Part One" post really tricked me…

    I always, ALWAYS forget to avoid blogs on April 1st :-)

  39. Mike Herman says:

    Wow… I was with you all the way down to "x ()= y means x = x(y)"  nodding and saying "not that useful, but interesting"… Looking back I should have known as soon as you released *any* details about .NET 5 :-)

  40. Stuart says:

    Another vote for making ??= real.

    Love the where= suggestion from Raymond too.

  41. Dennis Persson says:

    Aaah I was looking for good jokes all day and finally found one :)

  42. pminaev says:

    The omission of ->= to accompany .= is worrying. Is unsafe code being deprecated?

  43. pminaev says:

    As well, it seems that a number of common and useful scenarios are not covered by these handy shortcuts. For example, consider:

       x = !(x is y);

       x = -x(y);

      x = &x.y;

    I would therefore suggest that the op-assignment operator be made fully composable with arbitrary unary operators, such that the above could be properly rewritten as:

      x !is= y;

      x -()= y;

      x &.= y;

    respectively.

  44. Igor Ostrovsky says:

    Excellent addition to C#!

    I disagree with the meaning of the === operator, though. x === y is just syntactic sugar for x = (x == y), which is clearly just syntactic sugar for x = (x = (x = y))). This might be a breaking change from the previous version of C#, but it is necessary for consistency.

  45. Quick Joe Smith says:

    @Jonathan Pryor: Only if a parameterless constructor was defined for a given value type, otherwise array allocation should not be affected. I for one would like that choice. The only other choice, it seems, is to make it a class instead.

  46. HAHAHAHAHA says:

    Awesome. I'm sending this to my coworkers… on Monday

  47. SWeko says:

    I actually got to this sentence "We relax the restriction that x = Y be legal for built-in operators in both these cases, since it practically never will be" when it hit me.

    Eric would never use "practically never" in a compiler discussion, and then I checked the date :)

  48. Bos Luc says:

    Haha, nice one. You had me going for a few minutes there.

  49. Andrew says:

    That wasn't fair; I read this four days late.

  50. PiersH says:

    here I am again, one year later, still requesting the '??=' operator… Maybe April fools day isn't the best time for feature requests?

  51. Drizwar says:

    Although this was a joke, I think it would be good if C# implemented all of the functions of APL using the same single (often Greek) characters for expressing complex array transforms.

    This would complete the recent trends on making C# a much more complex language than [IMHO] it needs to be.

    It seems that the KISS principle has been abandoned, and that Scoitt Meyers' advice from nearly 20 years ago of "Dont MollyCoddle your users. Give them ONE way to accomplish a given function – When there are alternatives that do not have significant and obvious differences, then more time will be spend (when aggregating the developer community as a whole) on figuring out which is "Best", when in reality there is little or no benfit to one method over an other.

  52. penartur says:

    @Jon Skeet

    It seems that we should use brackets here, e.g. Console.WriteLine(x (==) 10) vs. Console.WriteLine(x =(=) 10) or Console.WriteLine(x (=)= 10). Someone smart out here should think up about operators precedence.

    @Raymond Chen

    Or even shorter, x where= condition.

  53. qwertman@hotmail.com says:

    You know, this made me think about how nice it would be to have a compund unary ! operator so I wouldn't have to write foo.Bar[baz] = !foo.Bar[baz]…

    then I realized that you can actually do this: foo.Bar[baz] ^= true. I accidentally learned something from this post! You can also use foo.Bar[baz] ^= -1 as an equivalent to the unary ~ operator.

    Now what about the compound unary – operator? I don't see how to accomplish that one. Eric, we need a " -==;" operator ;)