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;
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());
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.