Какой эффект оказывает модификатор virtual на член интерфейса?

Допустим, у меня есть интерфейс типа interface IThing { virtual string A => "A"; }. Какой эффект ключевое слово virtual оказывает на реализацию типов здесь? Удивительно, но я не смог найти ничего по этому поводу ни на страницах SO, dotnet GitHub и в обсуждениях, ни на learn.microsoft.com, возможно, скрытых под всем несвязанным контентом, где слова interface и virtual встречаются в любой комбинации. Судя по моему быстрому тестированию, никакого ощутимого эффекта это не дало. Моя интуиция подсказывает мне ожидать, что в иерархии реализующих классов будут представлены заданные виртуальные члены, как если бы базовый реализующий класс сам их объявил, но это не так.

Например:

class Thing: IThing
{   
}   
    
class Thing2: Thing
{
    override public string A => "!"; // ERROR: No suitable method found to override.
}

Если я объявлю свойство A на Thing, но не объявлю его явно виртуальным, оно все равно не скомпилируется. У меня также есть свобода просто определить их без модификатора, несмотря на то, что он присутствует в интерфейсе и компилируется. И код, использующий IThing, видит только реализацию A по умолчанию, независимо от того, что я делаю, если только класс не реализует интерфейс напрямую, а не через наследование в этой настройке.

Может ли кто-нибудь разъяснить использование модификатора virtual в элементах интерфейса? Я использую последнюю стабильную версию языка C#.

Методы, реализующие интерфейс, не являются virtual по умолчанию (см.: почему виртуальные методы разрешены при реализации методов интерфейса?). Но если они отмечены в интерфейсе знаком virtual, то становятся таковыми.

wohlstad 31.03.2024 13:31

@wohlstad дело в том, что Thing вообще не имеет реализации для IThing.A =)

Guru Stron 31.03.2024 13:42

@GuruStron не потому ли, что интерфейс содержит реализацию по умолчанию? Или вы имеете в виду, что метод интерфейса с реализацией по умолчанию всегда является виртуальным по умолчанию (но в любом случае может быть помечен как таковой для многословия)? Я не уверен, что понимаю этот момент в вашем ответе.

wohlstad 31.03.2024 13:48

@wohlstad «не потому ли, что интерфейс содержит реализацию по умолчанию» - да, именно так, и я бы сказал, что это делает вопрос немного другим по сравнению с тем, на который вы ссылаетесь.

Guru Stron 31.03.2024 13:49

Теперь я понимаю. Полезно знать (я не эксперт в C#). +1 к вашему ответу.

wohlstad 31.03.2024 13:53
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
5
84
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Следующее:

interface IThing { virtual string A => "A"; }

Является ли так называемый метод интерфейса по умолчанию и виртуальным, насколько я вижу, на самом деле ничего не делает, потому что:

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

Т.е. это просто явное заявление о том, что метод интерфейса является виртуальным, поскольку языковая команда решила не ограничивать такие вещи.

См. также часть спецификации Виртуальный модификатор против запечатанного модификатора:

Решения: Принятые в LDM 5 апреля 2017 г.:

  • не-virtual должно быть явно выражено через sealed или private.
  • sealed — ключевое слово, позволяющее сделать члены экземпляра интерфейса с телами не-virtual
  • Мы хотим разрешить все модификаторы в интерфейсах
  • ...

Обратите внимание, что реализованный по умолчанию IThing.A не является частью Thing, поэтому вы не можете сделать new Thing().A, вам нужно сначала выполнить приведение к интерфейсу.

Если вы хотите переопределить IThing.A в Thing2, вы можете реализовать интерфейс напрямую:

class Thing2 : Thing, IThing
{
     public string A => "!"; 
}

Console.WriteLine(((IThing)new Thing()).A); // Prints "A"
Console.WriteLine(((IThing)new Thing2()).A); // Prints "!"

Другой способ — объявить public virtual string A в Thing, чтобы ваш текущий код для Thing2 работал:

class Thing : IThing
{
    public virtual string A => "A";
}   
    
class Thing2 : Thing
{
    public override string A => "!"; 
}

Чтобы понять значение идентификаторов sealed/virtual в интерфейсах, вы можете создать второй интерфейс:

interface IThing {  string A => "A"; }
interface IThing2 : IThing {  string IThing.A => "B"; }
class Thing : IThing2 {}

Console.WriteLine(((IThing)new Thing()).A); // Prints "B"

А если вы объявите IThing.A как sealed, то IThing2 не будет компилироваться:

interface IThing { sealed string A => "A"; }

interface IThing2 : IThing
{
    // Does not compile:
    // string IThing.A => "B";
}

class AntoherThing : IThing
{
    // Does not compile too:
    string IThing.A => "B";
}

Также ознакомьтесь с почему виртуальный разрешен при реализации методов интерфейса? ссылка wohlstad в комментариях.

Явное объявление члена базового класса имеет досадный побочный эффект, заключающийся в невозможности использовать реализацию по умолчанию из интерфейса, верно?

ZzZombo 31.03.2024 13:58

@ZzZombo «Явное объявление члена базового класса» - если под явным объявлением вы подразумеваете фрагмент с Thing классом, имеющим A, то вы должны понимать, что без этого не будет «неявного» A в Thing (т. е. typeof(Thing).GetProperties().Length приведет к 0) . Также не уверен, что вы подразумеваете под «резервным вариантом», но если я правильно понимаю — то нет. И загляните stackoverflow.com/a/62334580/2501279 возможно это будет полезно.

Guru Stron 31.03.2024 14:04

Блестящий ответ! Первоначально я закрыл этот вопрос как дубликат stackoverflow.com/q/60640745/87698, но ваш ответ на самом деле дает конкретный пример, где виртуальный и запечатанный имеют значение, поэтому я отменил это.

Heinzi 31.03.2024 14:04

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

Heinzi 31.03.2024 14:06

@Heinzi да, тоже столкнулся с этим, добавлю это в ответ. Спасибо!

Guru Stron 31.03.2024 14:06

@GuruStron: я отредактировал комментарий, чтобы было понятнее. P.S.: Я имею в виду, что было бы жаль не иметь возможности повторно использовать реализацию по умолчанию, что делает ее вообще спорной.

ZzZombo 31.03.2024 14:07

Другие вопросы по теме

Как передать значение функции одного класса функции другого с помощью декоратора @property
TypeError: неподдерживаемые типы операндов для /: «свойство» и «комплекс»
Как я могу ссылаться на этот метакласс внутри метакласса, не указывая его имя в коде?
Как правильно реализовать структуры данных на C (в основном об указателях), если у вас есть опыт работы с Java. Например. правильно создать конструктор?
Почему функция в словаре, передающая аргументы, выполняется немедленно?
Непонимание классов в Python, доступ и изменение переменных экземпляра
Если у подкласса нет конструктора, как и у суперкласса, то почему я могу создать экземпляр подкласса?
Можно ли управлять внедрением bean-компонентов с помощью общих типов в Spring?
Как условно расширить класс с помощью методов/свойств в Typescript OOP
Каким должен быть тип параметра «данные» для конструктора класса в Typescript