Expressions breaking code when compiled using VS2015 Update 1
Asked Answered
S

1

21

After installing Visual Studio 2015 Update 1 on my machine I saw that some of my unit tests failed. After doing some investigation I was able to reduce the problem to this line of code:

Expression<Func<GameObject, bool>> expression = t => t.X == 0 && t.Y == 0 && t.GameObjectType == GameObjectType.WindMill;

When hovering over the expression variable the results were different in the versions of Visual Studio:

VS 2015: VS 2015

VS 2015 Update 1: VS 2015 Update 1

The logic that was doing the comparison for the enums (somewhere in ServiceStack.OrmLite code) now acted differently which then eventually resulted in the enum not being recognized as an enum, resulting in the failing unit test.

I was able to reproduce the problem using the following code:

class Program
{
    static void Main(string[] args)
    {
        var gameObjects = new List<GameObject> {
            new GameObject { X = 0, Y = 0, GameObjectType = GameObjectType.WindMill },
            new GameObject { X = 0, Y = 1, GameObjectType = GameObjectType.Pipe },
            new GameObject { X = 0, Y = 2, GameObjectType = GameObjectType.Factory }
        };

        var gameObjectsQueryable = gameObjects.AsQueryable();

        Expression<Func<GameObject, bool>> expression = t => t.X == 0 && t.Y == 0 && t.GameObjectType == GameObjectType.WindMill;

        var result = gameObjectsQueryable.Where(expression);

        var resultAsList = result.ToList();

        foreach (var item in resultAsList)
        {
            Console.WriteLine(item);
        }

        //Obtain the t.GameObjectType == GameObjectType.WindMill part
        var binaryExpression = expression.Body as BinaryExpression;
        var right = binaryExpression.Right;
        var binaryExpression2 = right as BinaryExpression;
        var right2 = binaryExpression2.Right;

        if (right2 is UnaryExpression)
        {
            Console.WriteLine("Found UnaryExpression (This happens when the solution is build with VS2015)...");

            var right2Unary = binaryExpression2.Right as UnaryExpression;
            var right2Constant = right2Unary.Operand as ConstantExpression;
            CheckIfConsantIsAsExpected(right2Constant);
        }
        else
        {
            Console.WriteLine("Found ConstantExpression (This happens when the solution is build with VS2015 Update 1)...");

            var right2Constant = binaryExpression2.Right as ConstantExpression;
            CheckIfConsantIsAsExpected(right2Constant);
        }

        Console.ReadKey();
    }

    public static void CheckIfConsantIsAsExpected(ConstantExpression expression)
    {
        if (expression.Value.Equals(GameObjectType.WindMill))
        {
            Console.WriteLine($"The value is the enum we expected :), : {expression.Value}");
        }
        else
        {
            Console.WriteLine($"The value is not the enum we expected :(, : {expression.Value}");
        }
    }
}

public class GameObject
{
    public int X { get; set; }
    public int Y { get; set; }
    public GameObjectType GameObjectType { get; set; }

    public override string ToString()
    {
        return $"{X},{Y}: {GameObjectType}";
    }
}

public enum GameObjectType
{
    WindMill = 100,
    Pipe = 200,
    Factory = 300
}

On VS 2015 it will go into the UnaryExpression path, and in VS 2015 Update 1 it will go into the ConstantExpression path.

If you compile the solution on VS 2015 and then copy the compiled .exe file to a VS 2015 Update 1 system it will run the same as the VS 2015 version (So also the UnaryExpression path). This suggests it is not JIT related but instead build related.

My question would be if this is intended? (Since it could break existing code when simply recompiling the solution)

Sitsang answered 5/12, 2015 at 0:37 Comment(6)
This looks like a pretty innocent optimization to me. I wouldn't expect it to break anything to the point of not working, though: sure, it would take a different path through the code, so tests that check which path is taken would break. However, the end result of evaluating this expression should stay the same, and the end result of translating it to something else should produce something equivalent.Numbat
Well the code that was breaking did serialization of enums to an SQL query. Now this breaks because it does not know that the value that's being handled is in fact an enum.Sitsang
Maybe you have a diferent assembly that contains that constant referenced into your expression? And you just did not rebuilt it properly ... just a thought. Are you sure that your enum stands in the same assembly ?Zoril
I have pretty much the same problem. Here, the problem happens in VS 2010 as well as in VS 2015 but it's fine in LinqPad.Atalie
This is a Roslyn induced mishap. Thousands of bugs, they've been chipping away at them so the version in Update 1 is quite different from the one in RTM and is pretty likely to have its own bag of defects. Just add your own bug report to the heap, click the New Issue button.Slowmoving
Did you check your code with an older version of Visual studio? like 2010. Because In 2010, Right is also Constant Expression.Deianira
C
2

This appears to be something that was actually broken with the RTM VS2015. If you compile it without the old version of Roslyn it is actually a ConstantExpression.

4.5 compiler: https://dotnetfiddle.net/XpKg10
Roslyn compiler: https://dotnetfiddle.net/zeGVdh

Contravention answered 15/3, 2016 at 12:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.