6
votes

Update: This is no longer an issue from C# 6, which has introduced the nameof operator to address such scenarios (see MSDN).

Note: Refer to “Getting names of local variables (and parameters) at run-time through lambda expressions” for a generalization of this question, as well as some answers.

I like the idea of using lambda expressions to create refactor-safe implementations of the INotifyPropertyChanged interface, using code similar to that provided by Eric De Carufel.

I’m experimenting with implementing something similar for providing the parameter name to an ArgumentException (or its derived classes) in a refactor-safe manner.

I have defined the following utility method for performing null checks:

public static void CheckNotNull<T>(Expression<Func<T>> parameterAccessExpression)
{
    Func<T> parameterAccess = parameterAccessExpression.Compile();
    T parameterValue = parameterAccess();
    CheckNotNull(parameterValue, parameterAccessExpression);
}

public static void CheckNotNull<T>(T parameterValue, 
    Expression<Func<T>> parameterAccessExpression)
{
    if (parameterValue == null)
    {
        Expression bodyExpression = parameterAccessExpression.Body;
        MemberExpression memberExpression = bodyExpression as MemberExpression;
        string parameterName = memberExpression.Member.Name;
        throw new ArgumentNullException(parameterName);
    }
}

Argument validation may then be performed in a refactor-safe manner using the following syntax:

CheckNotNull(() => arg);           // most concise
CheckNotNull(arg, () => args);     // equivalent, but more efficient

My concern lies in the following lines:

Expression bodyExpression = parameterAccessExpression.Body;
MemberExpression memberExpression = bodyExpression as MemberExpression;

A MemberExpression represents “accessing a field or property”. It is guaranteed to work correctly in the INotifyPropertyChanged case, since the lambda expression would be a property access.

However, in my code above, the lambda expression is semantically a parameter access, not a field or property access. The only reason the code works is that the C# compiler promotes any local variables (and parameters) that are captured in anonymous functions to instance variables within a compiler-generated class behind the scenes. This is corroborated by Jon Skeet.

My question is: Is this behaviour (of promoting captured parameters to instance variables) documented within the .NET specification, or is it just an implementation detail that may change in alternate implementations or future releases of the framework? Specifically, may there be environments where parameterAccessExpression.Body is MemberExpression returns false?

1
What about CheckNotNull(() => ((object)5) as string); ? - leppie
Personally I decided that whatever Contract.Requires<TException> does is good enough for me. If a precondition fails, it's a bug. No need to fret about the details. - CodesInChaos
@CodeInChaos: What about libraries that will be made available for consumption to third parties? It’s expected that public methods would perform argument-checking and throw ArgumentException with the correct parameter name. - Douglas
@leppie: My question concerns argument validation. The goal is to get the parameter name, not evaluate arbitrary expressions. - Douglas
Alternative here: stackoverflow.com/questions/869610/…, again not documented. But faster. - nawfal

1 Answers

0
votes

Closures: As you stated, for parameter access, the C# compiler (yes, specifically the compiler) creates a closure class that contains instance fields to store the value of your captured parameter variable. Could this change with future versions of the C# compiler? Sure. Maybe in a future version of C#, the generated closure classes will have randomly named variables since the name doesn't really matter at runtime. Further, the code you have might not work for other .NET languages. You'll notice that VB .NET generates expression trees and closure classes slightly differently from C# sometimes...

I am not sure if your current implementation will work for structs either (though I could be mis-remembering...the situation I'm thinking of dealing with boxing might only apply for Expression<Func<T, object>> (read, please try it youself).

Anyway...all this said...will it change in future versions of C#? Probably not. If it does, you could change your internal implementation to handle it probably...

As for performance: please be really careful here. You already said it would be more efficient to pass two arguments so you don't need to compile and evaluate the lambda....but just to be clear, you're talking about a 15 to 30ms hit here each time you compile and evaluate.