Why is getting a member name different between C # and VB.NET?

I have the following C # method:

private static string GetMemberName<T>(Expression<Func<T>> expr) { MemberExpression memberExpr = expr.Body as MemberExpression; if (memberExpr == null) throw new ArgumentOutOfRangeException("Wrong type of lambda..."); return memberExpr.Member.Name; } 

And I can use it to print the name of a class level field, method parameter or local var (note that this is pre-C # 6.0 nameof ):

 private static int _myFieldVar = 62; private static void DoStuff(int myMethodParam) { int myLocalVar = 2; Debug.Print(GetMemberName(() => myMethodParam)); // prints "myMethodParam" Debug.Print(GetMemberName(() => myLocalVar)); // prints "myLocalVar" Debug.Print(GetMemberName(() => _myFieldVar)); // _myFieldVariable } 

Now I want to convert this code to VB.NET, so here is the GetMemberName method:

 Private Function GetMemberName(Of T)(expr As Expression(Of Func(Of T))) As String Dim memberExpr As MemberExpression = TryCast(expr.Body, MemberExpression) If memberExpr Is Nothing Then _ Throw New ArgumentOutOfRangeException("Wrong type of lambda...") Return memberExpr.Member.Name End Function 

However, I notice different results when I get param parameter names and local variables, i.e. both have the prefix " $ VB $ Local _ ":

 Private _myFieldVar As Integer = 62 Private Sub DoThis(myMethodParam As Integer) Dim myLocalVar = 2 Debug.Print(GetMemberName(Function() myMethodParam)) ' prints "$VB$Local_myMethodParam"" Debug.Print(GetMemberName(Function() myLocalVar)) ' prints "$VB$Local_myLocalVar" Debug.Print(GetMemberName(Function() _myFieldVar)) ' prints "_myFieldVar()" End Sub 

I googled "$ VB $ Local_" and found this post which is very similar. However, I think my question is different from the fact that I am not getting this behavior with properties. If I call it:

 Debug.Print(GetMemberName(Function() MyProperty)) 

I get "MyProperty". In addition, my main question is: โ€œwhy is the behavior different from C # and VB.NET, that is, what is the meaning of" $ VB $ Local_ "and why is it missing in C #" , while this post takes care more about how to avoid this behavior in VB.NET.

+7
c # linq expression-trees
source share
1 answer

As mentioned by Hans Passant, 2 compilers use slightly different naming strategies to handle local variables in the Linq expression tree. Let me take a look at how both pins look decompiled. I used ILSpy with all options not checked in the View => Options => decompiler tab. I also simplified the output expression tree to keep the answer concise.

FROM#

  public static string Test() { int myLocalVar = 2; int myLocalVar2 = 2; // local varible never exported! note how it is not in the generated class myLocalVar2++; return GetMemberName(() => myLocalVar); } 

Exit

  [CompilerGenerated] private sealed class <>c__DisplayClass1_0 { public int myLocalVar; } public static string Test() { Class1.<>c__DisplayClass1_0 <>c__DisplayClass1_ = new Class1.<>c__DisplayClass1_0(); <>c__DisplayClass1_.myLocalVar = 2; int num = 2; num++; return Class1.GetMemberName<int>( Expression.Lambda<Func<int>>( Expression.MakeMemberAccess( Expression.Constant(<>c__DisplayClass1_, typeof(Class1.<>c__DisplayClass1_0)), typeof(Class1.<>c__DisplayClass1_0).GetMember("myLocalVar") ) ) ); } 

Vb

 Public Shared Function Test() As String Dim myLocalVar As Integer = 2 Dim myLocalVar2 As Integer = 2 myLocalVar2 = myLocalVar2 + 1 Return GetMemberName(Function() myLocalVar) End Function 

Exit

  [CompilerGenerated] internal sealed class _Closure$__2-0 { public int $VB$Local_myLocalVar; } public static string Test() { Class1._Closure$__2-0 closure$__2- = new Class1._Closure$__2-0(); closure$__2-.$VB$Local_myLocalVar = 2; int num = 2; num++; return Class1.GetMemberName<int>( Expression.Lambda<Func<int>>( Expression.MakeMemberAccess( Expression.Constant(closure$__2-, typeof(Class1._Closure$__2-0)), typeof(Class1._Closure$__2-0).GetMember("$VB$Local_myLocalVar") ) ) ); } 

Both compilers create a private sealed class for myLocalVar . This is done to meet the requirements of the Linq expression tree. The expression tree needs to write a link to a local variable. The example below shows why this is necessary.

  int localVar = 1; Expression<Func<int>> express = () => localVar; var compiledExpression = express.Compile(); Console.WriteLine(compiledExpression());//1 localVar++; Console.WriteLine(compiledExpression());//2 Console.ReadLine(); 

Back to the question - why is the behavior different from C # and VB.NET, that is, what is the value of "$ VB $ Local_" and why is it missing in C #?

Compilers generate an incredible amount of code for us, C # and VB.NET do this a little differently. So I'm just going to answer why VB inserts $VB$Local_ . ** To avoid name conflicts. ** Both C # DisplayClass and VB.Net Closure are used for several purposes. To avoid a collision, the name has a key prefix that represents the source. It just turns out that the C # key is nothing, all the other language features that contribute to the DisplayClass prefix with something else. Try decompiling the following VB.net to find out why the prefix key is needed.

 Sub Main() Dim myLocalVar As Integer = 2 Dim x1 As System.Action(Of Integer) = Sub(x) System.Console.WriteLine(x) GetMemberName(Function() myLocalVar) End Sub x1(2) End Sub 

The compiled closure will be as follows.

  [CompilerGenerated] internal sealed class _Closure$__2-0 { public int $VB$Local_myLocalVar; internal void _Lambda$__0(int x) { Console.WriteLine(x); Class1.GetMemberName<int>(Expression.Lambda<Func<int>>(Expression.Field(Expression.Constant(this, typeof(Class1._Closure$__2-0)), fieldof(Class1._Closure$__2-0.$VB$Local_myLocalVar)), new ParameterExpression[0])); } 

async and also expect to use closures this way, however they generate a lot of patterns that I did not want to embed here.


Concluding observations

Passing a local variable and parameter to a method named GetMemberName . Just luck was that under the hood 2 compilers automatically generate types and elements to satisfy linq expression trees in the first place. Fortunately, the last iteration of the compilers has an operator name, which is much better for solving this problem.

+1
source share

All Articles