Рассмотрите следующий код:
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;
}
}
(проигнорируйте дизайн класса; я на самом деле не пишу калькулятор! этот код просто представляет минимальную репродукцию для намного большей проблемы, которая взяла некоторое время для сужения),
Я ожидал бы это к также:
Вместо этого я бросил бессмысленное исключение в обозначенную строку. На 3,0 CLR это - NullReferenceException; на CLR Silverlight это - печально известная Операция, мог дестабилизировать время выполнения.
Это не приведет к ошибке времени компиляции, потому что является допустимым закрытием.
Проблема в том, что этот
еще не инициализирован на момент создания закрытия. Когда этот аргумент указан, ваш конструктор еще не запущен. Таким образом, результирующее 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) должен знать ищите его внутри крышки, чтобы люди не могли это сделать. Это не так, вот почему вы так и поступаете.
Это была исправленная ошибка компилятора. Код никогда не должен был быть законным с самого начала, и если мы собирались разрешить это, у нас должен был быть хотя бы сгенерированный действительный код. Виноват. Приносим извинения за неудобства.
Вы пробовали использовать вместо него () => операнд * операнд
? Проблема в том, что нет уверенности, что _operand будет установлен к моменту вызова базы. Да, он пытается закрыть ваш метод, и здесь нет никакой гарантии порядка вещей.
Поскольку вы вообще не устанавливаете _operand, я бы рекомендовал вместо этого просто использовать () => operand * operand
.
Как насчет этого:
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;
}
}