Вызов виртуального члена в конструкторе — почему один в порядке, а другой нет?

#c#

#c#

Вопрос:

С помощью приведенного ниже кода Resharper выдает предупреждение «вызов виртуального члена в конструкторе»:

 public class UserDetailViewModel : Screen
{
    public UserDetailViewModel()
    {
        // DisplayName is a virtual member of Screen
        DisplayName = "User Details"; 
    }
}
  

В то время как, если я изменю код таким образом, предупреждение исчезнет:

 public class UserDetailViewModel : Screen
{
    public UserDetailViewModel()
    {
        SetName();
    }

    private void SetName()
    {
        DisplayName = "User Details"; 
    }
}
  

Почему один выдает предупреждение, а другой нет? Является ли второй способ каким-то правильным, или он просто выходит за пределы того, что ReSharper может определить как потенциально опасное?

Ответ №1:

Это просто ограничение ReSharper. Он применяет множество эвристик, чтобы найти код, о котором он может предупредить, но он не найдет все. Для этого потребуется решить проблему остановки. Это невыполнимо.

Урок здесь довольно прост:
отсутствие предупреждения не означает, что предупреждать не о чем.

Комментарии:

1. Спасибо jalf. Чтобы избежать проблемы, допустимо ли изменить SetName() его на общедоступный и вызвать его после создания экземпляра, например: userDetailViewModel.SetName(); ? Или есть лучшее решение? (я знаю, это действительно должен быть новый вопрос)!

2. Можно обнаружить, что конструктор потенциально может вызывать виртуальный член, не выполняя анализ остановки.

3. @Town new UserDetailViewModel().SetName() — не самый лучший выбор, потому что ответственность за вызов возлагается на вызывающего абонента SetName() . Вместо этого я бы предпочел проигнорировать предупреждение.

4. @Hemal Pandya: мне тоже кажется неправильным, хотя игнорирование такого предупреждения кажется неправильным!

5. Почему DisplayName он должен быть виртуальным? Это то, что мне кажется странным. Если это часть инициализации объекта, то нет особых причин для того, чтобы он был виртуальным.

Ответ №2:

В Caliburn.Микро, обычно задается DisplayName путем переопределения метода OnInitialize(), например

     protected override void OnInitialize()
    {
        DisplayName = "User Details";
        base.OnInitialize();
    }
  

OnInitialize() вызывается только один раз, и вы больше не вызываете предупреждение в Resharper. Смотрите также здесь .

Ответ №3:

Предупреждение выдается из-за проблемы, которая может возникнуть в следующей ситуации

  • Создается объект производного типа UserDetailViewModel , скажем, «ConcreteModel»

  • ConcreteModel переопределяет Screen.DisplayName свойство. В методе set свойства это зависит от ConcreteModel того, завершено ли построенное, скажем, оно обращается к другому члену, который инициализирован в конструкторе.

В этом случае приведенный выше код вызовет исключение.

Правильный способ решить эту проблему — объявить DisplayName , что он находится sealed внутри UserDetailViewModel . Теперь вы можете быть уверены, что игнорировать предупреждение можно.

Это демонстрирует следующий пример. Раскомментирование строк в Der вызывает ошибку компиляции.

 class Base
{
    public virtual string DisplayName { get; set; }
}

class Der : Base
{
    public Der()
    {
        //  ok to ignore virtual member access here
        DisplayName = "Der";
    }
    public override sealed string DisplayName { get; set; }
}

class Leaf : Der
{ 
    private string _displayName;
    public Leaf()
    {
        _displayName = "default";
    }
    //override public string DisplayName
    //{
    //    get { return _displayName; }
    //    set { if (!_displayName.Equals(value)) _displayName = value; }
    //}
}