Общая информация о компиляторе: что является следствием этого кода - PullRequest
4 голосов
/ 13 августа 2011

Сегодня я просматривал некоторый код и наткнулся на некоторый код (точно изображенный этим фрагментом) ...

public abstract class FlargBase{
    public FlargBase(){
        this.DoSomething();
    }

    public abstract void DoSomething();
}

public class PurpleFlarg: FlargBase{
    public PurpleFlarg()
      : base(){
    }

    public override void DoSomething(){
        // Do something here;
    }
}

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

Мне было любопытно, потому что, на мой взгляд, могут произойти две вещи.

  1. Создание экземпляра базыКласс сделает вызов метода без определенной реализации.Я ожидаю, что компилятор выдаст ошибку или среда выполнения выдаст исключение из-за отсутствующей реализации.Я предполагаю, что компилятор обеспечивает реализацию {} Я неправильно набрал исходный код;он содержал абстрактное ключевое слово для класса.
  2. Создание экземпляра производного класса вызовет вызов метода в классе, который фактически еще не был создан.Я ожидал бы, что это вызовет исключение.

Этот код находился в производственной среде в течение нескольких месяцев.Очевидно, он работает достаточно правильно, и никто не заметил странного поведения.

Я надеюсь, что невероятный талант здесь, в StackOverflow, может дать мне некоторое представление о поведении и последствиях этого кода.

Ответы [ 5 ]

12 голосов
/ 13 августа 2011

AC # объект полностью построен и инициализирован в ноль перед запуском первого конструктора.Базовый конструктор будет вызывать производную реализацию виртуального метода.

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

Вы можете представить, что среда выполнения сначала вызывает самый производный конструктор.И его первым действием является неявный вызов базового конструктора.Я не уверен, реализован ли он на самом деле таким образом, но поскольку некоторые языки .net позволяют вызывать базовый конструктор в произвольной точке производного конструктора, я ожидаю, что C # просто вызовет конструктор базового класса в качестве первого действия производногоКонструктор.


Это поведение сильно отличается от того, как C ++ обрабатывает его.В C ++ производные классы конструируются один за другим, и до запуска конструктора производного класса объект по-прежнему имеет тип базового класса, и переопределения из производного класса игнорируются.

2 голосов
/ 13 августа 2011

В C # методы переопределения всегда разрешаются в наиболее производную реализацию.Пример приведен в 10.11.3 (выполнение конструктора) спецификации C # здесь :

Инициализаторы переменных преобразуются в операторы присваивания, и эти операторы присваивания выполняются до вызоваконструктора экземпляра базового класса.Это упорядочение гарантирует, что все поля экземпляра инициализируются инициализаторами их переменных перед выполнением любых операторов, имеющих доступ к этому экземпляру.

Учитывая пример

using System;

class A
{
   public A() {
      PrintFields();
   }

   public virtual void PrintFields() {}

}

class B: A
{
   int x = 1;
   int y;

   public B() {
      y = -1;
   }

   public override void PrintFields() {
      Console.WriteLine("x = {0}, y = {1}", x, y);
   }
}

при использовании нового B ()чтобы создать экземпляр B, создается следующий вывод:

x = 1, y = 0
2 голосов
/ 13 августа 2011

Ваш PurpleFlarg.DoSomething() выполняется перед телом конструктора PurpleFlarg().

Это может привести к неожиданностям, так как общее предположение всегда состоит в том, что конструктор является первым методом, работающим с объектом.

Вот страница MSDN с примером условия «ошибка».

0 голосов
/ 14 августа 2011

Что ж, этот шаблон действительно полезен для переопределяемых фабрик объектов в реальности, поэтому случай, подобный приведенному в следующем коде, кажется мне совершенно законным и хорошо написанным.

abstract class MyBase
{
    public object CustomObject { get; private set; }

    public MyBase()
    {
        this.CustomObject = this.CreateCustomObject();
    }

    protected abstract object CreateCustomObject();
}

class MyBaseList : MyBase
{
    protected override object CreateCustomObject()
    {
        return new List<int>();
    }
}

class MyBaseDict : MyBase
{
    protected override object CreateCustomObject()
    {
        return new Dictionary<int, int>();
    }
}
0 голосов
/ 13 августа 2011

Если класс содержит абстрактный метод (DoSomething), то класс также должен быть абстрактным и не может быть создан.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...