Update: This is no longer an issue from C# 6, which has introduced the nameof
operator to address such scenarios (see MSDN).
It appears that the answer to my question is no; the feature is non-standardized. The situation seems even bleaker than I’d originally suspected; not only is the promotion of captured variables non-standardized, but so is the entire specification of converting anonymous functions to their expression tree representations.
The implication of this is that even straightforward anonymous functions, such as the below, are not guaranteed to result in consistent expression trees across different implementations of the framework (until the conversion is standardized):
Expression<Func<int, int, int>> add = (int x, int y) => x + y;
The following excerpts are taken from the C# Language Specification 4.0 (emphasis added in all cases).
From “4.6 Expression tree types”:
The exact definition of the generic type Expression<D>
as well as the precise rules for constructing an expression tree when an anonymous function is converted to an expression tree type, are both outside the scope of this specification, and are described elsewhere.
From “6.5.2 Evaluation of anonymous function conversions to expression tree types”:
Conversion of an anonymous function to an expression tree type produces an expression tree (§4.6). More precisely, evaluation of the anonymous function conversion leads to the construction of an object structure that represents the structure of the anonymous function itself. The precise structure of the expression tree, as well as the exact process for creating it, are implementation defined.
The third example in “6.5.3 Implementation example” demonstrates the conversion of an anonymous function that captures a local variable, and confirms the variable promotion mentioned in my question:
The lifetime of the local variable must now be extended to at least the lifetime of the anonymous function delegate. This can be achieved by “hoisting” the local variable into a field of a compiler generated class. Instantiation of the local variable (§7.15.5.2) then corresponds to creating an instance of the compiler generated class, and accessing the local variable corresponds to accessing a field in the instance of the compiler generated class.
This is further corroborated at the end of the section:
The same technique applied here to capture local variables can also be used when converting anonymous functions to expression trees: References to the compiler generated objects can be stored in the expression tree, and access to the local variables can be represented as field accesses on these objects. The advantage of this approach is that it allows the “lifted” local variables to be shared between delegates and expression trees.
However, there is a disclaimer at the beginning of the section:
The implementation described here is based on the same principles used by the Microsoft C# compiler, but it is by no means a mandated implementation, nor is it the only one possible. It only briefly mentions conversions to expression trees, as their exact semantics are outside the scope of this specification.
P.S. Eric Lippert confirms in this comment that the expression tree specs were never shipped. There exists an Expression Trees v2 Spec under the DLR documentation on CodePlex, but its scope does not appear to cover the conversion of anonymous functions to expression trees in C#.