C# - закрытия по полям класса в инициализаторе?

Рассмотрите следующий код:

using System;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            var square = new Square(4);
            Console.WriteLine(square.Calculate());
        }
    }

    class MathOp
    {        
        protected MathOp(Func<int> calc) { _calc = calc; }
        public int Calculate() { return _calc(); }
        private Func<int> _calc;
    }

    class Square : MathOp
    {
        public Square(int operand)
            : base(() => _operand * _operand)  // runtime exception
        {
            _operand = operand;
        }

        private int _operand;
    }
}

(проигнорируйте дизайн класса; я на самом деле не пишу калькулятор! этот код просто представляет минимальную репродукцию для намного большей проблемы, которая взяла некоторое время для сужения),

Я ожидал бы это к также:

  • распечатайте "16", ИЛИ
  • бросьте ошибку времени компиляции, если закрытие по членскому полю не позволяется в этом сценарии

Вместо этого я бросил бессмысленное исключение в обозначенную строку. На 3,0 CLR это - NullReferenceException; на CLR Silverlight это - печально известная Операция, мог дестабилизировать время выполнения.

12
задан Richard Berg 15 March 2010 в 23:59
поделиться

4 ответа

Это не приведет к ошибке времени компиляции, потому что является допустимым закрытием.

Проблема в том, что этот еще не инициализирован на момент создания закрытия. Когда этот аргумент указан, ваш конструктор еще не запущен. Таким образом, результирующее NullReferenceException на самом деле вполне логично. Это это , это null !

Я вам это докажу. Давайте перепишем код так:

class Program
{
    static void Main(string[] args)
    {
        var test = new DerivedTest();
        object o = test.Func();
        Console.WriteLine(o == null);
        Console.ReadLine();
    }
}

class BaseTest
{
    public BaseTest(Func<object> func)
    {
        this.Func = func;
    }

    public Func<object> Func { get; private set; }
}

class DerivedTest : BaseTest
{
    public DerivedTest() : base(() => this)
    {
    }
}

Угадайте, что это печатает? Да, это true , замыкание возвращает null , потому что this не инициализируется при выполнении.

Править

Мне было любопытно заявление Томаса, я подумал, что, возможно, они изменили поведение в следующем выпуске VS. Я действительно обнаружил проблему Microsoft Connect именно по этому поводу. Он был закрыт как «не исправит». Странный.

Как сообщает Microsoft в своем ответе, обычно недопустимо использовать ссылку this из списка аргументов при вызове базового конструктора; ссылка просто не существует в этот момент времени, и вы фактически получите ошибку времени компиляции, если попытаетесь использовать ее «голым». Так что, возможно, он должен вызвать ошибку компиляции для случая закрытия, но ссылка this скрыта от компилятора, который (по крайней мере, в VS 2008) должен знать ищите его внутри крышки, чтобы люди не могли это сделать. Это не так, вот почему вы так и поступаете.

11
ответ дан 2 December 2019 в 05:40
поделиться

Это была исправленная ошибка компилятора. Код никогда не должен был быть законным с самого начала, и если мы собирались разрешить это, у нас должен был быть хотя бы сгенерированный действительный код. Виноват. Приносим извинения за неудобства.

14
ответ дан 2 December 2019 в 05:40
поделиться

Вы пробовали использовать вместо него () => операнд * операнд ? Проблема в том, что нет уверенности, что _operand будет установлен к моменту вызова базы. Да, он пытается закрыть ваш метод, и здесь нет никакой гарантии порядка вещей.

Поскольку вы вообще не устанавливаете _operand, я бы рекомендовал вместо этого просто использовать () => operand * operand .

0
ответ дан 2 December 2019 в 05:40
поделиться

Как насчет этого:

using System;
using System.Linq.Expressions;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            var square = new Square(4);
            Console.WriteLine(square.Calculate());
        }
    }

    class MathOp
    {
        protected MathOp(Expression<Func<int>> calc) { _calc = calc.Compile(); }
        public int Calculate() { return _calc(); }
        private Func<int> _calc;
    }

    class Square : MathOp
    {
        public Square(int operand)
            : base(() => _operand * _operand)
        {
            _operand = operand;
        }

        private int _operand;
    }
}
2
ответ дан 2 December 2019 в 05:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: