20

I’m interested in retrieving the names of local variables (and parameters) at run-time in a refactor-safe manner. I have the following extension method:

public static string GetVariableName<T>(Expression<Func<T>> variableAccessExpression)
{
    var memberExpression = variableAccessExpression.Body as MemberExpression;
    return memberExpression.Member.Name;
}

…which returns the name of the variable captured through a lambda expression:

static void Main(string[] args)
{
    Console.WriteLine(GetVariableName(() => args));
    // Output: "args"

    int num = 0;
    Console.WriteLine(GetVariableName(() => num));
    // Output: "num"
}

However, this only works because the C# compiler promotes any local variables (and parameters) that are captured in anonymous functions to instance variables of the same name within a compiler-generated class behind the scenes (per Jon Skeet). If this were not the case, the cast of Body to MemberExpression would fail, since MemberExpression represents field or property access.

Is this variable promotion documented behaviour, or is it an implementation detail subject to change in other versions of the framework?

Note: This question is a generalization of my former one on argument validation.

Community
  • 1
  • 1
Douglas
  • 53,759
  • 13
  • 140
  • 188
  • You can catch alternatives here: http://stackoverflow.com/questions/72121/finding-the-variable-name-passed-to-a-function-in-c-sharp but then again undocumented. The anonymous type approach is much faster though.. – nawfal Apr 17 '13 at 13:21

3 Answers3

12

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#.

Community
  • 1
  • 1
Douglas
  • 53,759
  • 13
  • 140
  • 188
  • @rally25rs: Good point. My assumption is that Microsoft *do* have an internal spec which they follow for all their implementations, but they chose not to commit to making it standardized publicly. Meaning it is almost certainly safe to assume that the conversions relied upon in other parts of the framework (such as Linq2Sql) will remain consistent; however, this reassurance does not extend to expressions in general (such as the expression mentioned in my question for reading parameter names). – Douglas Jun 17 '12 at 13:09
4

This is a behavior you should not rely upon.

Take a look at Abuse of C# lambda expressions or Syntax brilliance?

Now read the comments from Eric Lippert who is on the C# design team. They include:

I just asked Anders (and the rest of the design team) what they thought. Let's just say the results would not be printable in a family-friendly newspaper

and

As for why this is horrid, we could start with unobvious, clever (remember, clever is bad, clever code is hard to maintain), not at all within the by-design use cases envisaged by the designers of lambdas, slow, brittle, unportable, and unnecessary

From these statements I would say that it will not become a documented or supported behavior.

Community
  • 1
  • 1
ErnieL
  • 5,773
  • 1
  • 23
  • 27
  • +1: This pointed me in the right direction on what the answer would be. However, in the linked question, the `.Attributes(style => "width:100%")` lambda appears to be used only because it looks aesthetically/stylistically better than the traditional `.Attributes("style", "width:100%")` syntax, and not because it introduces any functionality otherwise unavailable, justifying Lippert’s comment that it is “unnecessary”. (continued…) – Douglas Jun 17 '12 at 10:04
  • 3
    In my question, I feel there is a stronger use-case for getting the member/variable name using lambdas. I find that the .NET practice of embedding member/variable names into strings – such for `PropertyChangedEventArgs` and `ArgumentException` – is more “horrid” than my proposed alternative. – Douglas Jun 17 '12 at 10:05
  • Prism4 uses this same thing for its Expression tree overload of RaisePropertyChanged. – JKor Jun 17 '12 at 22:23
  • @JKor: Thanks for the pointer! I’ve seen quite a few online sources using lambda expressions for implementing `INotifyPropertyChanged`, but hardly any for `ArgumentException`. Which is understandable, since it seems that the approach is much less likely to break for properties/fields than for parameters/variables. – Douglas Jun 28 '12 at 21:37
  • 1
    Eric’s and Anders’ strongly worded replies are … *puzzling*, to put it politely. C++ for instance standardises this capture behaviour and there doesn’t seem an obvious reason for not doing so. Essentially calling reliance on it an “unobvious, brittle, inefficient, unnecessary hack” is not only insulting, it’s also factually wrong. It’s none of these things, it’s simply a solution to a real problem. – Konrad Rudolph Oct 03 '13 at 22:57
1

AFAIK, it's an implementation detail.

However, I think you can bet it won't actually change.

I just tested in VS2012 RC and it works as expected - so you're safe for a couple of years at least.

Nick Butler
  • 24,045
  • 4
  • 49
  • 70
  • 1
    What about Mono and alternate implementations of the framework? They’ve been known to take some liberties with the implementation details (for example, they provide [looser semantics on the memory model](http://stackoverflow.com/a/10608993/1149773) than the .NET Framework). Even if they happen to use the same implementation today, they might not do so in future releases. – Douglas Jun 16 '12 at 13:16
  • 1
    Still, thanks for checking on VS2012 :-) Good to know that at least Microsoft won’t be breaking it yet. – Douglas Jun 16 '12 at 13:25
  • I don't know about Mono - you would have to check. It's a toss up really: do you think the chance that your solution will break is greater than the chance you'll get human errors with `CheckNotNull( "args", args )` – Nick Butler Jun 16 '12 at 13:28
  • 1
    That’s a tricky question. I think there’s a greater chance of some hard-coded names becoming invalid (mostly following automated renaming of parameters) than of the lambda expression’s behind-the-scenes implementation changing. However, the consequences of the former are relatively mild (the `ArgumentException` would display an incorrect name), whilst those of the latter would be more severe (all parameter names would fail to be read at all). – Douglas Jun 16 '12 at 13:35
  • 1
    I agree, although **if** the lambda solution breaks because of compiler implementation changes, it will break big and so it would be obvious what to fix. IMHO, this is better than a few smaller bugs that would go unnoticed. – Nick Butler Jun 16 '12 at 13:47
  • 1
    You have a valid point. My main reluctance is that lambda expressions are a “non-traditional” approach for reading parameter names – all MSDN samples hard-code their names into strings – so if I’m going to deviate from the norm, I need to be able to justify the new approach’s robustness. The possibility of it breaking in other versions of the framework reduces the incentive to switch to it, despite its advantages over the traditional approach. – Douglas Jun 16 '12 at 14:55