Почему лямбда без захвата изменилась с статического в С# 5 на метод экземпляра в С# 6?

Этот код генерирует исключение в отмеченной строке:

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);
        }
    }
}

Теперь я протестировал этот код в VS2013 (работает как шарм) и в VS2015 Community (генерирует исключение).

Я следил за .Net Reference Source, который привел меня к некоторому кодовому условию, которое проверяет, предоставлен ли метод IsStatic или нет.

В моем случае метод я pass (a.Method) является статическим в VS2013 и по какой-то причине нестационарный (экземпляр) в VS2015. Если нет, то он подсказывает, что я не представил аргумент Instance.

Почему так? Как этого можно избежать, чтобы Expression.Call снова начал работать в новой Visual Studio?

Ответ 1

У меня нет ответа, почему это так (воспроизводится локально тоже).

Однако ответ на вопрос:

Почему так? Как этого можно избежать, чтобы Expression.Call снова начнете работать в новой Visual Studio?

Вы можете сделать это (работает на обоих компиляторах):

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);
}

Благодаря Jeppe Stig Nielsen за исправление в отношении a.Target

Ответ 2

Roslyn (компилятор С#, используемый VS 2015) изменил все методы лямбда на нестатические методы, независимо от того, захватывают ли они переменные или нет. См. Делегировать изменения поведения кэширования в Roslyn. Поскольку я объясняю, это допустимое поведение, потому что анонимные методы (например, те, которые возникают здесь), которые не фиксируют переменные, имеют меньше требований к жизненному циклу, чем те, которые делают. Это не означает, однако, что эти методы должны быть статическими: это всего лишь деталь реализации.

Ответ 3

Почему это так?

Я не знаю, почему, и честно не знал об этом изменении, но, быстро взглянув на декомпилированный код, показал, что для всех подобных лямбда внутри класса Roslyn генерирует методы instance в одноэлементном вложенный класс под названием <>c, как этот

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);
        }
    }
}

Для меня это потрясающее изменение, но я не нашел никакой информации об этом.

Как насчет того, как заставить ваш код работать, я думаю, что ответ @Rob охватывает эту часть.