Чтобы ответить на ваш вопрос в комментарии:
Мне любопытно, почему это работает в конструкторе, а не в моем примере.
Порядок построения объекта C # выглядит следующим образом. Сначала выполняются все инициализаторы полей, от до самого производного класса . Таким образом, если у вас есть класс B и производный класс D, и вы создаете новый D, то все инициализаторы поля D запускаются перед любым инициализатором поля B.
После запуска всех инициализаторов полей конструкторы работают в порядке от наименее производного до наиболее производного . То есть сначала выполняется тело конструктора B, а затем тело конструктора D.
Это может показаться странным, но рассуждения просты:
Прежде всего, очевидно, что тела ctor базового класса должны выполняться перед телами ctor производного класса. Производные ctors могут зависеть от состояния, инициализированного ctor базового класса, но вряд ли будет верно обратное; базовый класс обычно не знает о производном классе.
Во-вторых, очевидно, что ожидается, что инициализированные поля будут иметь значения до того, как будут запущены тела конструктора. Было бы очень странно, чтобы конструктор наблюдал неинициализированное поле, когда прямо там есть инициализатор поля.
Следовательно, способ, которым мы кодируем генерирование ctors, заключается в том, что каждый ctor следует шаблону: «Инициализируйте мои поля, затем вызовите мой ctor базового класса, затем выполните мой ctor». Поскольку каждый следует этому шаблону, все поля инициализируются в порядке от производного до базового, и все тела ctor выполняются от базового к производному.
Хорошо, теперь, когда мы установили это, что происходит, когда инициализатор поля ссылается на "this" явно или неявно ? Почему ты бы так поступил? «this», вероятно, будет использоваться для доступа к методу, полю, свойству или событию объекта, чьи инициализаторы полей не все запустились, и ни одно из тел конструктора не запустилось! Очевидно, что это невероятно опасно . Большая часть состояния, необходимого для корректной работы класса, все еще отсутствует.
Поэтому мы запрещаем любую ссылку на "this" в любом инициализаторе поля.
К тому времени, как вы дойдете до определенного тела конструктора, вы уже знаете, что все инициализаторы полей уже запущены и что все конструкторы для всех ваших базовых классов уже запущены. У вас гораздо больше доказательств того, что объект, вероятно, находится в хорошем состоянии, поэтому «этот» доступ разрешен в теле конструктора. (Вы по-прежнему можете делать глупо опасные вещи; например, вы можете вызывать виртуальный метод, переопределенный в производном классе, когда он находится внутри конструктора базового класса; производный конструктор еще не запущен, и производный метод может завершиться с ошибкой. Будьте осторожны! )
Теперь вы можете вполне разумно сказать, что есть большая разница между:
class D : B
{
int x = this.Whatever(); // call a method on the base class, whose ctor has not run!
и
class D : B
{
Func<int> f = this.Whatever;
или аналогично:
class D : B
{
Func<int> f = ()=>this.Whatever();
Это ничего не значит. Он не читает ни одно состояние, которое может быть неинициализировано. Очевидно, это совершенно безопасно. Мы могли бы создать правило, которое гласит: «Разрешить этот доступ в инициализаторе поля, когда он находится в преобразовании метода-группы-делегата или в лямбду», верно?
Неа. Это правило допускает конечную пробежку вокруг системы безопасности:
class D : B
{
int x = ((Func<int>)this.Whatever)();
И мы снова в одной лодке. Таким образом, мы могли бы создать правило, которое гласит: «разрешить этот доступ в инициализаторе поля, когда доступ осуществляется в методе преобразования группы в делегат или в лямбду, и анализатор потока может доказать, что делегат не вызывается до запуска конструктора». "И, эй, теперь команда разработчиков языка и команды по разработке, разработке, тестированию и обучению компиляторов тратят огромное количество времени, денег и усилий на решение проблемы, которую мы не хотим решать в первую очередь. место.
Для языка лучше иметь простые, понятные правила, которые способствуют безопасности и могут быть правильно реализованы, легко протестированы и четко документированы, чем иметь сложные правила, позволяющие работать неясным сценариям. Простое, безопасное правило: инициализатор поля экземпляра не может иметь никакой явной или неявной ссылки на 'this', точка.
Дальнейшее чтение:
http://blogs.msdn.com/b/ericlippert/archive/2008/02/15/why-do-initializers-run-in-the-opposite-order-as-constructors-part-one.aspx
http://blogs.msdn.com/b/ericlippert/archive/2008/02/18/why-do-initializers-run-in-the-opposite-order-as-constructors-part-two.aspx