我有以下C#方法:
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;
}
我可以像这样使用它来打印类级别字段,方法参数或本地var的名称(注意这是前C#6.0 nameof
operator):
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
}
现在我想将此代码转换为VB.NET,所以这里是GetMemberName
方法:
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
但是,当我得到方法参数和局部变量名称时,我注意到不同的结果,即它们都以&#34; $ VB $ Local _ &#34;为前缀:
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
我用Google搜索&#34; $ VB $ Local _&#34;并发现this post非常相似。但是,我认为我的问题不同,因为我没有通过属性获得此行为。如果我这样称呼:
Debug.Print(GetMemberName(Function() MyProperty))
我得到了&#34; MyProperty&#34;。而且,我的基本问题是&#34;为什么C#和VB.NET之间的行为不同,即&#34; $ VB $ Local _&#34;的含义是什么?为什么它在C#&#34; 中不存在,而那篇文章更关心如何在VB.NET中避免这种行为。
答案 0 :(得分:1)
正如Hans Passant所提到的,2个编译器使用稍微不同的命名策略来处理Linq表达式树中的局部变量。让我们来看看两个输出看起来像反编译。我使用了ILSpy,在View =&gt;中未选中所有选项。选项=&gt;反编译选项卡。我还简化了输出表达式树,以使答案简洁明了。
<强> C#强>
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);
}
输出
[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
输出
[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")
)
)
);
}
两个编译器都为private sealed class
创建myLocalVar
。这样做是为了满足Linq表达式树的要求。表达式树需要捕获对局部变量的引用。以下示例说明了为什么需要这样做。
int localVar = 1;
Expression<Func<int>> express = () => localVar;
var compiledExpression = express.Compile();
Console.WriteLine(compiledExpression());//1
localVar++;
Console.WriteLine(compiledExpression());//2
Console.ReadLine();
回到问题 - 为什么C#和VB.NET之间的行为不同,即“$ VB $ Local_”的含义是什么?为什么它在C#中不存在?
编译器为我们生成了令人难以置信的代码量,C#和VB.NET都略微不同。所以我只想回答为什么VB插入$VB$Local_
。 **避免名称冲突。** C#的DisplayClass和VB.Net的Closure都用于多个目的。为防止发生冲突,名称前缀为代表源的键。事实证明,C#的关键是什么,所有其他语言功能都有助于DisplayClass前缀与其他东西。尝试反编译下面的VB.net,看看为什么需要前缀密钥。
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
编译后的闭包将如下。
[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和await也以这种方式使用闭包,但是它们生成了很多样板代码,我不想在这里粘贴。
最后评论
将局部变量和参数传递给名为GetMemberName
的方法。只有幸运的是,2个编译器首先自动生成类型和成员以满足linq表达式树。幸运的是,编译器的最新版本具有nameof运算符,这是处理此问题的更好方法。