C# handles both nested and chained expressions, obviously. If the nesting and/or chaining is linear then it's evident what order the expressions are evaluated in:
Foo(Bar(Baz().Bop()))
can only evaluate in the following order:
Baz()
Bop()
Bar()
Foo()
But what if the nesting isn't linear? Consider: Foo(Baz()).Bar(Bop())
Clearly the following MUST all be true:
Baz
before Foo
Foo
before Bar
Bop
before Bar
But it's not clear exactly when Bop
will be evaluated.
Any of the following would be a viable order:
Bop()
Baz()
Foo()
Bar()
Baz()
Bop()
Foo()
Bar()
Baz()
Foo()
Bop()
Bar()
My instinct is that the 3rd option is likely correct. i.e. that it will fully evaluate Foo(Baz())
before it starts to evaluate any of .Bar(Bop())
Whilst I could certainly test an individual situation to see what happens, that doesn't tell me whether my guess will always be true?
But my question is: Is the order of evaluation of branched nested expressions defined as part of the C# language specification, or left to the situational judgement of the compiler?
If not, is it at least known to be deterministic?
You'll find the answers in Section 11 of the specification.
Specifically, 11.6.6 Function member invocation says:
The run-time processing of a function member invocation consists of the following steps, where
M
is the function member and, ifM
is an instance member,E
is the instance expression:
...
E
is evaluated. If this evaluation causes an exception, then no further steps are executed.- The argument list is evaluated as described in §11.6.2.
So, given an expression E.M(A)
, E
is fully evaluated before A
is evaluated.
For the Foo(Baz()).Bar(Bop())
case, if we're looking at the evaluation of Bar
(so E
is Foo(Baz())
, M
is Bar
and the argument list is Bop()
), this means that Foo
(E
) must have been fully evaluated before Bop
(the argument list) is evaluated, meaning that "possibility #3" is the correct one.
There's also 11.6.2.3 Run-time evaluation of argument lists:
During the run-time processing of a function member invocation (§11.6.6), the expressions or variable references of an argument list are evaluated in order, from left to right
So in the expression M(A, B)
, A
is fully evaluated before B
is evaluated.