Chaining simple assignments is not so simple

Chaining simple assignments is not so simple

Rate This
  • Comments 46

UPDATE: I interrupt this episode of FAIC with a request from my friend and colleague Lucian, from the VB team, who wonders whether it is common in C# to take advantage of the fact that assignment expressions are expressions. The most common usage of this pattern is the subject of this blog entry: the fact that "chained" assignment works at all is a consequence of the fact that assignments are expressions, not statements. There are other uses too; one could imagine something like "return this.myField = x;" as a short cut for "this.myField = x; return this.myField;" -- perhaps we are performing some computation and then recording the results for use later. Or perhaps we've got something like myNonNullableString = (myNullableString = Foo()) ?? "<null>"; -- there are any number of ways this idiom could be used.

I do not use this idiom myself; I'm of the opinion that side effects such as assignments are best represented by putting each in a statement of its own, rather than as something embedded in a larger expression. My question for you is: do you use assignments as expressions? If so, how and why? Note that I am looking for mundane, "real world" examples of this pattern, not clever ideas about how this could in theory be used. If you've got one, please leave it in the comments and I'll pass it along to Lucian. Thanks!

*********************

Today I examine another myth about C#. Consider the following code:

a = b = c;

This is legal; you can make arbitrarily long chains of simple assignments. This pattern is most often seen in something like

int i, j, k;
i = j = k = 123;

I often hear that this works “because assignment is right-associative and results in the value of the right-hand side”.

Well, that’s half true. It is right-associative; obviously this has to be equivalent to

i = (j = (k = 123)));

It doesn’t make any sense to parenthesize it from the left. Now, in this particular example, the statement is true, but in general it is not. The result of the simple assignment operator is not the value of the right hand side:

const int x = 10;
short y;
object z;
z = y = x;
System.Console.WriteLine(z.GetType().ToString());

This prints “System.Int16”, not “System.Int32”. The value of the right-hand side of “y = x” is clearly an int, but we do not assign a reference to a boxed int to z, we assign a reference to a boxed short!

So then is the correct statement “… results in the value of the left-hand side”?

Nope, that’s not right either, and we can prove it.

class C
{
  private string x;
  public string X {
    get { return x ?? ""; }
    set { x = value; } }
  static void Main()
  {
    C c = new C();
    object z;
    z = c.X = null;
    System.Console.WriteLine(z == null);
    System.Console.WriteLine(c.X == null);
  }
}

This prints “True / False” – the result of the assignment operator is not the value of the left-hand-side. The value of the left hand side is the empty string but the value of the operator is null.

Heck, the left hand side need not even have a value. Write-only properties are weird and rare, but legal; if there were no getter then the left hand side c.X would not have a value!

The correct statement should now be pretty easy to deduce: the result of the simple assignment operator is the value that was assigned to the left-hand side.

 

  • I use assignments as expressions often, for lazy loading of readonly properties:

    private MyObject myObject = null;

    public MyObject MyObject {

       return myObject ?? (myObject = Repository.GetMyObject());

    }

  • @pete.d: Indeed, I've used a Dictionary<Type, Func<Object, ...>> in other scenarios. But a map seems like overkill when the number of types is very small.

  • Re: map vs if

    Well, you couldn't use a dictionary in your example.  Your code prioritizes the types, and includes a type that can't actually be used in a search (IEnumerable).  That's why my example uses an array instead (initialized in priority order).

    But note that in cases where a dictionary would apply, you could also write that as a switch (for types, you'd have to switch on .GetType().FullName).  And if you do that, the compiler's just going to convert that to a dictionary anyway, if you have more than six choices (for C# 3.0 anyway).

    That said, if you really love the if/else if pattern, it works fine and I certainly wouldn't argue that there's anything wrong with it per se.  And even though these days, in those kinds of situations I do in fact write the assignment as a separate statement, I've in the past been known to include it in the if statement itself.  Seems like a fine use of that construction, if that's what you prefer.

  • Re: "do you use assignments as expressions?"

    As others have mentioned, I use using statements with the result of an initialisation as the paramter (which I suspect is something different to an assignment, strictly speaking.)

    I would use the while ((s = Read()) != null) pattern. but I don't really like the look of it with all those brackets and = operators where == operators normally go. So I use the while (true) { s = Read(); if (s == null) break; /*...*/} pattern instead.

    I wouldn't mind some syntactic sugar for that while loop to look nicer. Does C#3 or 4 provide anything to help out there? (Something like an anonymous function that returns something foreach can use.)

  • As other commentors have done, I've written the following code for Streams:

       static byte[] BufferedReadAll(Stream stream)

       {

           byte[] buffer = new byte[16*1024];

           using (MemoryStream ms = new MemoryStream())

           {

               int bytesRead;

               while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)

               {

                   ms.Write(buffer, 0, bytesRead);

               }

               return ms.ToArray();

           }

       }

    and for ASP.NET MVC, I've seen this for alternate rows:

       public static void AlternateRows<T>(this IEnumerable<T> dataSource, Action<T, bool> func)

       {

           if (dataSource == null)

               return;

           bool rowState = true;

           foreach (var item in dataSource)

               func(item, (rowState = !rowState));

       }

  • Another possible use of the assignment operator:

    A a = ...;

    B b;

    C c;

    D d;

    if ((b = a.GetB()) != null &&

       (c = b.GetC()) != null &&

       (d = c.GetD()) != null &&

       d.IsE)

    {

       // Do something if a.GetB().GetC().GetD().IsE is true...

    }

    else

    {

       // Do something else...

    }

  • I've (only recently) started using them for simple property initializers.  I only use it where the initialization of the field would be a simple, single line, as otherwise this could cause some serious maintenance headaches.  My first exposure to the style was from ReSharper.

    What used to be:

        private string _foo;

        public string Foo

        {

            get

            {

                if (_foo == null)

                {

                    _foo = ComplexFooCreator.GetMeSomeFoo();

                }

                return _foo;

            }

        }

    is now:

        private string _foo;

        public string Foo

        {

            get { return _foo ?? (_foo = ComplexFooCreator.GetMeSomeFoo());}

        }

    (Not knowing if your blog does tabs or spaces, I've done both. And boy does that hurt my sensibilities.)

  • The use of such constructs is simply out of laziness...and most programmers seem to be inherently lazy.  Why be clear in your code when you can be quick, especially when your boss wants you to get everything out yesterday?

  • RobS: I found the following in my code this morning:

       min = max = ComputeInitialValue();

    What would be clearer? This:

       max = ComputeInitialValue();

       min = max;

    Or is that too lazy? Maybe you think it should be this:

       readonly var initialValue = ComputeInitialValue();

       min = initialValue;

       max = initialValue;

    BTW, only bad programmers aren't inherently lazy. But laziness isn't the topic here. When I see something like "while ((str = reader.ReadLine()) != null) {...}", I don't think it's the programmer being lazy; I think that it's just more clear than "while (true) { str = reader.ReadLine(); if (str == null) { break; } ...}". Making the guard condition on the loop explicit makes the loop easier to understand. I can clearly see that it's a loop to read all the lines in a stream rather than a loop that just happens to read a line from a stream at the beginning of every iteration.

  • Common use of assignment in expression (particular case is C++, but has C# analogues):

    if (FAILED(hr = CoDoSomething())) {

     LogError(hr);

     return;

    }

    But, is the result of the assignment expression actually a value, or actually a reference to the left-hand side variable?  In C++, it is the latter (barring really unusual override of operator=), so you can, for example, initialize a pointer with its address.  I don't use pointers in C# that much, maybe the syntax prevents any case where you would actually be able to discern the difference between value and reference to assigned variable.

  • How about a wording like this: "the result of the simple assignment operator is the resultant value that was assigned to the left-hand side."

    This would handle the case where a property getter returns a different value to what was passed to the setter.

  • >> The result of the simple assignment operator is not the value of the right hand side:

    >> const int x = 10;

    >> short y;

    >> object z;

    >> z = y = x;

    >> System.Console.WriteLine(z.GetType().ToString());

    You have missed one important point, if you read the language specification. The type of expression

    y = x

    is short, not int. Because here compiler put in an implicit cast. The effective statement is

    z = y = (short)x;

    So it is always correct that "results in the value of the right-hand side”. Never half!

    HTH

  • Eric,

    Here's one situation were I routinely use assignments as expressions (one of the only places that I feel it's less messy than the alternatives):

    Dictionary<string, HashSet<Member>> _blacklistedMembersByPlace = new Dictionary<string, HashSet<Member>>();

    public bool BlacklistMember(string place, Member member)

    {

    HashSet<Member> members;

    if (!_blacklistedMembersByPlace.TryGetValue(place, out members))

    _blacklistedMembersByPlace.Add(place, members = new HashSet<Member>());

    return members.Add(members);

    }

  • The only place I've ever used assignments as expressions in C# (besides chaining) is when matching a single input string against several possible regular expressions. My code will then generally look something like this:

    Match m;

    if ((m = Regex.Match(...)).Success) {

     // Process, using m.Groups

    } else if ((m = Regex.Match(...)).Success) {

     // Process, using m.Groups

    }

    The important thing to note here is that the code inside the if needs access to m.Groups, otherwise I could just use Regex.IsMatch().

    Of course I realise that I could write it like this:

    Match m = Regex.Match(...);

    if (m.Success) {

     // Process, using m.Groups

    } else {

     m = Regex.Match(...);

     if (m.Success) {

       // Process, using m.Groups

     }

    }

    but once you have three or four regular expressions, the first one starts looking quite a lot cleaner.

  • This is Java code, just for note, but the simplest use of assignment chaining is when new objects are required.

           primaryframe.add(

               treescrollpane = new javax.swing.JScrollPane(

                   datatree = new javax.swing.JTree(

                       treemodel = new javax.swing.tree.DefaultTreeModel(

                           node

                       )

                   )

               ),

               java.awt.BorderLayout.WEST

           );

    Here you can see that I chained quite a few assignments in the one expression. Now, you can argue that the need to do this in the first place is an example of bad design - I certainly won't argue that Java isn't badly designed - but the example is the same. Assignment chaining works well when you need both a variable AND a value.

Page 3 of 4 (46 items) 1234