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; на Silverlight CLR это печально известный операция может дестабилизировать выполнения.

4 ответов


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

проблема в том, что this еще не инициализирован во время создания закрытия. Ваш конструктор фактически еще не запущен, когда этот аргумент предоставлен. Итак, результат NullReferenceException на самом деле вполне логично. Это this это 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 говорит в своем ответе, обычно недопустимо использовать the this ссылка из списка аргументов вызова базового конструктора; ссылка просто не существует в этот момент времени, и вы фактически получите ошибку времени компиляции, если попытаетесь использовать ее "голый".- Так, возможно, это должны создайте ошибку компиляции для случая закрытия, но this ссылка скрыта от компилятора, который (по крайней мере в VS 2008) бы знать посмотреть для его замыкания для того чтобы предотвратить людей от этого этот. Это не так, вот почему вы заканчиваете с этим поведением.


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


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

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

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

поскольку вы не устанавливаете _operand вообще, я бы рекомендовал просто использовать .