为什么在C#和VB.NET?

时间:2016-03-31 16:23:39

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

我有以下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中避免这种行为。

1 个答案:

答案 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运算符,这是处理此问题的更好方法。