Why do lambda expressions in VB differ from C#?

2019-06-15 08:05发布

I just came across a bug in NHibernate which happens to already be raised: https://nhibernate.jira.com/browse/NH-2763

I'm not sure if this applies to anything else other than enums but when using a Lambda from VB, it looks different to the same Lambda from C#.

C#:

Where(x => x.Status == EmployeeStatus.Active)

VB

Where(Function(x) x.Status = EmployeeStatus.Active)

They are the same as far as I'm aware? (My VB isn't great)

If I put a break point on the same line of code, where the above code is passed into. In C# I get:

C# version

On the same line when VB version is passed in, I get:

VB version

Is this something I'm doing wrong? Is the result's the same, just displayed different between C#/VB?

Edit: Ok so they are displayed different, but they can't be the same because NHibernate cannot handle it. The C# version is handled perfectly fine by NHibernate, the VB version resolves in the following exception being thrown:

Exception

The NHibernate StackTrace:

   at NHibernate.Impl.ExpressionProcessor.FindMemberExpression(Expression expression) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\ExpressionProcessor.cs:line 168
   at NHibernate.Impl.ExpressionProcessor.ProcessSimpleExpression(Expression left, Expression right, ExpressionType nodeType) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\ExpressionProcessor.cs:line 323
   at NHibernate.Impl.ExpressionProcessor.ProcessSimpleExpression(BinaryExpression be) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\ExpressionProcessor.cs:line 316
   at NHibernate.Impl.ExpressionProcessor.ProcessBinaryExpression(BinaryExpression expression) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\ExpressionProcessor.cs:line 418
   at NHibernate.Impl.ExpressionProcessor.ProcessExpression(Expression expression) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\ExpressionProcessor.cs:line 486
   at NHibernate.Impl.ExpressionProcessor.ProcessExpression[T](Expression`1 expression) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\ExpressionProcessor.cs:line 504
   at NHibernate.Criterion.QueryOver`2.Add(Expression`1 expression) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Criterion\QueryOver.cs:line 635
   at NHibernate.Criterion.QueryOver`2.NHibernate.IQueryOver<TRoot,TSubType>.Where(Expression`1 expression) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Criterion\QueryOver.cs:line 686
   at *removed*.EmployeeRepository.GetByEntityId(Int64 entityId, Expression`1 basicCriteria) in D:\*removed*\EmployeeRepository.cs:line 76

So something must be different between the two?

Edit 2:

For Jonathan. This is the method where the expression is used:

public IEnumerable<Employee> GetByEntityId(long entityId, Expression<Func<Employee, bool>> basicCriteria)
{
    IEnumerable<Employee> result;

    using (var tx = Session.BeginTransaction())
    {
        var employeeQuery = Session.QueryOver<Employee>()
                                    .Where(x => x.EntityId == entityId);

        if (basicCriteria != null)
            employeeQuery = employeeQuery.Where(basicCriteria);

        result = employeeQuery.List();

        tx.Commit();
    }

    return result;
}

标签: c# vb.net lambda
3条回答
爷的心禁止访问
2楼-- · 2019-06-15 08:37

Yes its just displayed different. You do nothing wrong. The VB inline methods have another syntax and also IDE integration

查看更多
狗以群分
3楼-- · 2019-06-15 08:46

The part with <>__DisplayClass means the compiler created a closure. That means the expression in the debugger is not the one you showed but rather something like

var status = EmployeeStatus.Active;
Expression<Func<Employee, bool>> expr = x => x.Status == status;

But this is not the part NHibernate is having problems with. The difference between Convert and ConvertChecked is. And that is caused by a difference in semantics between C# and VB.NET:

In C#, by default, all runtime computations are unchecked, that is, they are not checked for arithmetic overflows. You can change the default for a particular piece of code using checked.

In VB, the default is to have computations checked, which results in different generated lambda. I'm sure there are ways to change this in VB too.

So the following C# code creates the same lambda as your VB:

checked
{
    Expression<Func<Employee, bool>> expr = x => x.Status == EmployeeStatus.Active;
}

EDIT: If you don't find another option, as a last resort, you could rewrite the expression VB.NET generates into a form using Convert instead of ConvertChecked:

Class UncheckedVisitor
    Inherits ExpressionVisitor

    Protected Overrides Function VisitUnary ( _
        node As UnaryExpression _
    ) As Expression
        If node.NodeType = ExpressionType.ConvertChecked
            node = Expression.Convert(node.Operand, node.Type, node.Method)
        End If
        Return MyBase.VisitUnary(node)
    End Function
End Class

unchechedVisitor.Visit(expr) then returns expr with all instances of ConvertChecked replaced with Convert.

查看更多
Summer. ? 凉城
4楼-- · 2019-06-15 08:48

The difference you are seeing has nothing to do with lambdas; it is simply a difference in the semantics of the languages. VB is emitting calls to functions that by default throw exceptions if an integer overflows (hence the Checked part of the name).

By default the C# compiler does not emit the "checked" version of functions, and apparently NHibernate is developed by C# users, so it doesn't seem to recognize the "checked" functions.

If you go to the Compile options for your project and click on Advanced Compile Options, you can check the "Remove integer overflow checks" box so that VB has the default C# behavior and you shouldn't get that error anymore:

Screenshot of dialog showing option

查看更多
登录 后发表回答