【问题标题】:Why does getting a member expression member name differ between C# and VB.NET?为什么在 C# 和 VB.NET 之间获取成员表达式成员名称不同?
【发布时间】:2016-03-31 16:23:38
【问题描述】:

我有以下 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;
}

我可以像这样使用它来打印类级字段、方法参数或本地变量的名称(注意这是 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

但是,当我获得方法参数和局部变量名称时,我注意到不同的结果,即它们都以“$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

我搜索了“$VB$Local_”并找到了非常相似的this post。但是,我认为我的问题有所不同,因为我没有通过属性获得这种行为。如果我这样称呼:

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

我得到“我的财产”。此外,我的基本问题是“为什么 C# 和 VB.NET 之间的行为不同,即“$VB$Local_”的含义是什么,为什么在 C# 中不存在”,而那篇文章更关心如何在 VB.NET 中避免这种行为。

【问题讨论】:

  • 但是理论上都编译成同一个IL,那为什么会以不同的方式存储呢?为什么Member.Name 在一种语言与另一种语言中的值不同?
  • 您只是看到 C# 和 VB.NET 编译器用于命名捕获的变量成员的不同策略。是的,VB.NET 编译器将“$VB$Local”作为变量名的前缀。使用 ildasm.exe 很容易看到,注意 _Closure__2-0 类的成员。这是一个纯粹的实现细节,可能会由您的代码公开。
  • 是的,重点是本地人(和参数)根本不是调用类的成员,您正在查看的是捕获。
  • @HenkHolterman -- 所以我不想将expr.Body 转换为MemberExpression?在这种情况下,我找不到任何有关访问“捕获”的信息...
  • 看汉斯的评论,你的成员表达式是指生成类的成员。 VB 编译器可以随意生成。

标签: c# vb.net linq expression-trees c#-to-vb.net


【解决方案1】:

正如 Hans Passant 所提到的,这两个编译器使用稍微不同的命名策略来处理 Linq 表达式树中的局部变量。让我们看看这两个输出的反编译后的样子。我使用 ILSpy 并在 View => Options => decompiler 选项卡中未选中所有选项。我还稍微简化了输出表达式树以保持答案简洁。

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")
                )
            )
        );
    }

两个编译器都为myLocalVar 创建一个private sealed class。这样做是为了满足 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 运算符,这是处理此问题的更好方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-12
    • 1970-01-01
    • 2017-03-27
    相关资源
    最近更新 更多