hazzik / DelegateDecompiler

A library which is able to decompile a delegate or a method body to its lambda representation
MIT License
522 stars 62 forks source link

Computed - Null Propagation Operator decompiling problems in Visual Studio 2019 #146

Closed gansmmm closed 4 years ago

gansmmm commented 5 years ago

Operator ?? in Computed property decompiled into GetValueOrDefault(). If use EntityFramework this this behavior throw exception.

Code

internal class Program
    {
        private static void Main(string[] args)
        {
            var q = new[] { new TEntity() }
                .AsQueryable()
                .Select(x => new
                {
                    x.Computed
                })
                .Decompile();
        }
    }
    public class TEntity
    {
        public int? Int { get; set; }
        [Computed]
        public int Computed => Int ?? 0;
    }

Decompiled to in VS 2017 Community version 15.9.11

.Call System.Linq.Queryable.Select(
    .Constant<System.Linq.EnumerableQuery`1[DelDecVs2019.TEntity]>(DelDecVs2019.TEntity[]),
    '(.Lambda #Lambda1<System.Func`2[DelDecVs2019.TEntity,<>f__AnonymousType0`1[System.Int32]]>))

.Lambda #Lambda1<System.Func`2[DelDecVs2019.TEntity,<>f__AnonymousType0`1[System.Int32]]>(DelDecVs2019.TEntity $x) {
    .New <>f__AnonymousType0`1[System.Int32]($x.Int ?? 0)
}

in VS 2019 Community version 16.0.2

.Call System.Linq.Queryable.Select(
    .Constant<System.Linq.EnumerableQuery`1[DelDecVs2019.TEntity]>(DelDecVs2019.TEntity[]),
    '(.Lambda #Lambda1<System.Func`2[DelDecVs2019.TEntity,<>f__AnonymousType0`1[System.Int32]]>))

.Lambda #Lambda1<System.Func`2[DelDecVs2019.TEntity,<>f__AnonymousType0`1[System.Int32]]>(DelDecVs2019.TEntity $x) {
    .New <>f__AnonymousType0`1[System.Int32](.Call ($x.Int).GetValueOrDefault())
}

C# 7.3 .Net Framework 4.7.2

edwardforgacs commented 5 years ago

Exact same issue here, thanks for the detailed description.

tl24 commented 5 years ago

I was able to find a work around. If you replace 0 with a variable the compiler can't replace it with GetValueOrDefault(). I'm guessing it can't be a const otherwise it might get optimized away.

public class TEntity
{
    public int? Int { get; set; }
    private static readonly int Zero = 0;
    [Computed]
    public int Computed => Int ?? Zero;
}
tl24 commented 5 years ago

I have a potential fix for this, do you take pull requests? Any guidance on how to submit those?

I added this code to DelegateDecompiler.Processor.BuildMethodCall:

            if (m.Name == "GetValueOrDefault" && m.DeclaringType.IsGenericType && m.DeclaringType.GetGenericTypeDefinition() == typeof(Nullable<>) && arguments.Length == 0)
            {
                return Expression.Coalesce(instance, ExpressionHelper.Default(Nullable.GetUnderlyingType(instance.Type)));
            }

TestGroup15Aggregation.Test02Sum.TestSumCountInChildrenWhereChildrenCanBeNone was failing for me in VS 2019 before the added change. It passed afterwards.

edwardforgacs commented 5 years ago

I'm getting a lot of other test failures as a result of this change, does it seem to be working for everyone else?

ronaldme commented 5 years ago

@hazzik Could you please look into this issue / pull request? I can't work in VS2019 on some projects where I am using this library and more people seem to be having problems.