Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do assignment statements return a value?

Tags:

syntax

c#

To my understanding, assignment s = "Hello"; should only cause "Hello" to be assigned to s, but the operation shouldn’t return any value.

Your understanding is 100% incorrect. Can you explain why you believe this false thing?

What is the reasoning behind allowing assignment statements to return a value?

First off, assignment statements do not produce a value. Assignment expressions produce a value. An assignment expression is a legal statement; there are only a handful of expressions which are legal statements in C#: awaits of an expression, instance construction, increment, decrement, invocation and assignment expressions may be used where a statement is expected.

There is only one kind of expression in C# which does not produce some sort of value, namely, an invocation of something that is typed as returning void. (Or, equivalently, an await of a task with no associated result value.) Every other kind of expression produces a value or variable or reference or property access or event access, and so on.

Notice that all the expressions which are legal as statements are useful for their side effects. That's the key insight here, and I think perhaps the cause of your intuition that assignments should be statements and not expressions. Ideally, we'd have exactly one side effect per statement, and no side effects in an expression. It is a bit odd that side-effecting code can be used in an expression context at all.

The reasoning behind allowing this feature is because (1) it is frequently convenient and (2) it is idiomatic in C-like languages.

One might note that the question has been begged: why is this idiomatic in C-like languages?

Dennis Ritchie is no longer available to ask, unfortunately, but my guess is that an assignment almost always leaves behind the value that was just assigned in a register. C is a very "close to the machine" sort of language. It seems plausible and in keeping with the design of C that there be a language feature which basically means "keep on using the value that I just assigned". It is very easy to write a code generator for this feature; you just keep on using the register that stored the value that was assigned.


Haven’t you provided the answer? It’s to enable exactly the kinds of constructs you have mentioned.

A common case where this property of the assignment operator is used is reading lines from a file...

string line;
while ((line = streamReader.ReadLine()) != null)
    // ...

My favorite use of assignment expressions is for lazily initialized properties.

private string _name;
public string Name
{
    get { return _name ?? (_name = ExpensiveNameGeneratorMethod()); }
}

For one, it allows you to chain your assignments, as in your example:

a = b = c = 16;

For another, it allows you to assign and check a result in a single expression:

while ((s = foo.getSomeString()) != null) { /* ... */ }

Both are possibly dubious reasons, but there are definitely people who like these constructs.


Aside from the reasons already mentioned (assignment chaining, set-and-test within while loops, etc), to properly use the using statement you need this feature:

using (Font font3 = new Font("Arial", 10.0f))
{
    // Use font3.
}

MSDN discourages declaring the disposable object outside of the using statement, as it will then remain in scope even after it has been disposed (see the MSDN article I linked).


I'd like to elaborate on a specific point Eric Lippert made in his answer and put the spotlight on a particular occasion that hasn't at all been touched upon by anyone else. Eric said:

[...] an assignment almost always leaves behind the value that was just assigned in a register.

I'd like to say that the assignment will always leave behind the value we tried to assign to our left operand. Not just "almost always". But I don't know because I haven't found this issue commented in the documentation. It might theoretically be a very effective implemented procedure to "leave behind" and not reevaluate the left operand, but is it efficient?

'Efficient' yes for all the examples so far constructed in the answers of this thread. But efficient in the case of properties and indexers that use get- and set accessors? Not at all. Consider this code:

class Test
{
    public bool MyProperty { get { return true; } set { ; } }
}

Here we have a property, which isn't even a wrapper for a private variable. Whenever called upon he shall return true, whenever one tries to set his value he shall do nothing. Thus whenever this property is evaluated, he shall be truthy. Let's see what happens:

Test test = new Test();

if ((test.MyProperty = false) == true)
    Console.WriteLine("Please print this text.");

else
    Console.WriteLine("Unexpected!!");

Guess what it prints? It prints Unexpected!!. As it turns out, the set accessor is indeed called, which does nothing. But thereafter, the get accessor is never called at all. The assignment simply leaves behind the false value we tried to assign to our property. And this false value is what the if statement evaluates.

I'll finish off with a real world example that got me researching this issue. I made an indexer which was a convenient wrapper for a collection (List<string>) that a class of mine had as a private variable.

The parameter sent to the indexer was a string, which was to be treated as a value in my collection. The get accessor would simply return true or false if that value existed in the list or not. Thus the get accessor was another way to use the List<T>.Contains method.

If the indexer's set accessor was called with a string as an argument and the right operand was a bool true, he would add that parameter to the list. But if the same parameter was sent to the accessor and the right operand was a bool false, he would instead delete the element from the list. Thus the set accessor was used as a convenient alternative to both List<T>.Add and List<T>.Remove.

I thought I had a neat and compact "API" wrapping the list with my own logic implemented as a gateway. With the help of an indexer alone I could do many things with a few set of keystrokes. For instance, how can I try to add a value to my list and verify that it's in there? I thought this was the only line of code necessary:

if (myObject["stringValue"] = true)
    ; // Set operation succeeded..!

But as my earlier example showed, the get accessor which is supposed to see if the value really is in the list wasn't even called. The true value was always left behind effectively destroying whatever logic I had implemented in my get accessor.