Why do assignment statements return a value?
This is allowed:
int a, b, c;
a = b = c = 16;
string s = null;
while ((s = "Hello") != null) ;
To my und开发者_运维百科erstanding, assignment s = ”Hello”;
should only cause “Hello”
to be assigned to s
, but the operation shouldn’t return any value. If that was true, then ((s = "Hello") != null)
would produce an error, since null
would be compared to nothing.
What is the reasoning behind allowing assignment statements to return a value?
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.
If assignment didn't return a value, the line a = b = c = 16
wouldn't work either.
Also being able to write things like while ((s = readLine()) != null)
can be useful sometimes.
So the reason behind letting assignment return the assigned value, is to let you do those things.
I think you're misunderstanding how the parser is going to interpret that syntax. The assignment will be evaluated first, and the result will then be compared to NULL, i.e. the statement is equivalent to:
s = "Hello"; //s now contains the value "Hello"
(s != null) //returns true.
As others have pointed out, the result of an assignment is the assigned value. I find it hard to imagine the advantage to having
((s = "Hello") != null)
and
s = "Hello";
s != null;
not be equivalent...
I think the main reason is the (intentional) similarity with C++ and C. Making the assigment operator (and a lot of other language constructs) behave like their C++ counterparts just follows the principle of least surprise, and any programmer coming from another curly-bracket language can use them without spending much thought. Being easy to pick up for C++ programmers was one of the main design goals for C#.
For the two reasons you include in your post
1) so you can do a = b = c = 16
2) so you can test if an assignment succeeded
if ((s = openSomeHandle()) != null)
The fact that 'a++' or 'printf("foo")' may be useful either as a self-contained statement or as a part of a larger expression means that C has to allow for the possibility that expression results may or may not be used. Given that, there's a general notion that expressions which might usefully 'return' a value may as well do so. Assignment chaining can be slightly "interesting" in C, and even more interesting in C++, if all the variables in question do not have precisely the same type. Such usages are probably best avoided.
An extra advantage I don't see given in the answers here, is that the syntax for assignment is based on arithmetic.
Now x = y = b = c = 2 + 3
means something different in arithmetic than a C-style language; in arithmetic its an assertion, we state that x is equal to y etc. and in a C-style language it's an instruction that makes x equal to y etc. after it is executed.
This said, there's still enough relation between the arithmetic and the code that it doesn't make sense to disallow what is natural in the arithmetic unless there's a good reason. (The other thing that C-style languages took from the use of the equals symbol is the use of == for equality comparison. Here though because the right-most == returns a value this sort of chaining would be impossible.)
Another great example use-case, I use this all the time:
var x = _myVariable ?? (_myVariable = GetVariable());
//for example: when used inside a loop, "GetVariable" will be called only once
I like using the assignment return value when I need to update a bunch of stuff and return whether or not there were any changes:
bool hasChanged = false;
hasChanged |= thing.Property != (thing.Property = "Value");
hasChanged |= thing.AnotherProperty != (thing.AnotherProperty = 42);
hasChanged |= thing.OneMore != (thing.OneMore = "They get it");
return hasChanged;
Be careful though. You might think you can shorten it to this:
return thing.Property != (thing.Property = "Value") ||
thing.AnotherProperty != (thing.AnotherProperty = 42) ||
thing.OneMore != (thing.OneMore = "They get it");
But this will actually stop evaluating the or statements after it finds the first true. In this case that means it stops assigning subsequent values once it assigns the first value that is different.
See https://dotnetfiddle.net/e05Rh8 to play around with this
精彩评论