31

This code throws an exception on the marked line:

using System;
using System.Linq.Expressions;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Action<int, int> a = (x, y) => Console.WriteLine(x + y);

            ParameterExpression p1 = Expression.Parameter(typeof(int), "p1");
            ParameterExpression p2 = Expression.Parameter(typeof(int), "p2");

            // Here is the exception ArgumentNullException.
            MethodCallExpression call = Expression.Call(a.Method, p1, p2);
        }
    }
}

Now, I've tested this code in VS2013 (works like a charm) and in VS2015 Community (throws the exception).

I followed the .Net Reference Source, which led me to some code condition which checkes whether the supplied method IsStatic or not.

In my case, the method I pass (a.Method) is static in VS2013 and for some reason non static (instance) in VS2015. If not, it throws, telling me that I did not supply the Instance argument.

Why is it so? How can this be avoided so that Expression.Call would begin to work again in new Visual Studio?

Johnbot
  • 2,169
  • 1
  • 24
  • 24
AgentFire
  • 8,944
  • 8
  • 43
  • 90
  • 8
    Probably difference between how Roslyn compiler (included in VS2015) generates classes for lambdas. You might want to report an issue on github: https://github.com/dotnet/roslyn – MarcinJuraszek Nov 26 '15 at 23:11
  • 2
    Personally, I wouldn't be relying upon implementation details such as this. Do the correct thing and either define your own method or invoke the delegate. – Lukazoid Nov 27 '15 at 00:46
  • 1
    @Lukazoid which I do. When on Earth calling a lambda method became illegal? – AgentFire Nov 27 '15 at 13:04
  • 4
    @AgentFire You are not invoking the lambda, you are calling the underlying compiler generated method, if you want to invoke the lambda you want to be using something like `Expression.Invoke(Expression.Constant(a), p1, p2)` – Lukazoid Nov 27 '15 at 13:21
  • @AgentFire I only put my comment there as a warning that this should not be relied upon, that said I think this is a good question, I just don't want someone relying upon the implementation mistakenly. If you look at my highest voted answer, it is for a very similar style of question: http://stackoverflow.com/a/25604041/921321 – Lukazoid Nov 27 '15 at 13:37
  • 1
    I just ran into this also. It seems my call to `Expression.Convert(Expression, typeof({SomeType}), conversionMethodInfo);` fails now because of this. `conversionMethodInfo` was created from a lambda, but is now no longer considered static. – James Wilkins Apr 04 '17 at 20:27
  • @JamesWilkins right? Try Lukazoid's solution and tell me the results :) – AgentFire Apr 05 '17 at 11:25
  • There is no solution: http://stackoverflow.com/questions/43217853/how-to-create-a-static-lambda-for-use-with-expression-building – James Wilkins Apr 05 '17 at 17:11

3 Answers3

22

Roslyn (the C# compiler used by VS 2015) changed all lambda methods to non-static methods, whether they capture variables or not. See Delegate caching behavior changes in Roslyn. As I explain, this is an allowed behavior because anonymous methods (like those at issue here) that don't capture variables have fewer lifetime requirements than those that do. This doesn't mean, though, that those methods must be static: this is merely an implementation detail.

Community
  • 1
  • 1
Peter O.
  • 32,158
  • 14
  • 82
  • 96
10

I don't have an answer as to why that is so (reproduced locally, too).

However, the answer to:

Why is it so? How can this be avoided so that Expression.Call would begin to work again in new Visual Studio?

You can do this (works on both compilers):

Action<int, int> a = (x, y) => Console.WriteLine(x + y);

ParameterExpression p1 = Expression.Parameter(typeof(int), "p1");
ParameterExpression p2 = Expression.Parameter(typeof(int), "p2");

MethodCallExpression call;
if (a.Method.IsStatic)
{
    call = Expression.Call(a.Method, p1, p2);
}
else
{
    call = Expression.Call(Expression.Constant(a.Target), a.Method, p1, p2);
}

Thanks to Jeppe Stig Nielsen for fix regarding a.Target

Community
  • 1
  • 1
Rob
  • 26,989
  • 16
  • 82
  • 98
4

Why is it so?

I don't know why, and honestly was unaware of that change, but taking a quick look at the decompiled code showed that for all similar lambdas inside the class Roslyn generates instance methods in a singleton nested class called <>c like this

internal class Program
{
    [CompilerGenerated]
    [Serializable]
    private sealed class <>c
    {
        public static readonly Program.<>c <>9;
        public static Action<int, int> <>9__0_0;

        static <>c()
        {
            Program.<>c.<>9 = new Program.<>c();
        }

        internal void <Main>b__0_0(int x, int y)
        {
            Console.WriteLine(x + y);
        }
    }
}

To me this is a breaking change, but I didn't find any information about that.

What about how to make your code working, I think @Rob answer covers that part.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343