Допустим, у меня есть интерфейс типа 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#.
@wohlstad дело в том, что Thing
вообще не имеет реализации для IThing.A
=)
@GuruStron не потому ли, что интерфейс содержит реализацию по умолчанию? Или вы имеете в виду, что метод интерфейса с реализацией по умолчанию всегда является виртуальным по умолчанию (но в любом случае может быть помечен как таковой для многословия)? Я не уверен, что понимаю этот момент в вашем ответе.
@wohlstad «не потому ли, что интерфейс содержит реализацию по умолчанию» - да, именно так, и я бы сказал, что это делает вопрос немного другим по сравнению с тем, на который вы ссылаетесь.
Теперь я понимаю. Полезно знать (я не эксперт в C#). +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 «Явное объявление члена базового класса» - если под явным объявлением вы подразумеваете фрагмент с Thing
классом, имеющим A
, то вы должны понимать, что без этого не будет «неявного» A
в Thing
(т. е. typeof(Thing).GetProperties().Length
приведет к 0
) . Также не уверен, что вы подразумеваете под «резервным вариантом», но если я правильно понимаю — то нет. И загляните stackoverflow.com/a/62334580/2501279 возможно это будет полезно.
Блестящий ответ! Первоначально я закрыл этот вопрос как дубликат stackoverflow.com/q/60640745/87698, но ваш ответ на самом деле дает конкретный пример, где виртуальный и запечатанный имеют значение, поэтому я отменил это.
Кстати, вам не нужно использовать второй интерфейс, чтобы увидеть разницу.
@Heinzi да, тоже столкнулся с этим, добавлю это в ответ. Спасибо!
@GuruStron: я отредактировал комментарий, чтобы было понятнее. P.S.: Я имею в виду, что было бы жаль не иметь возможности повторно использовать реализацию по умолчанию, что делает ее вообще спорной.
Методы, реализующие интерфейс, не являются
virtual
по умолчанию (см.: почему виртуальные методы разрешены при реализации методов интерфейса?). Но если они отмечены в интерфейсе знакомvirtual
, то становятся таковыми.