Почему C# реализует методы как невиртуальные по умолчанию?

Listener является признаком, поэтому Box<Listener> действительно действительно объект признака, Box<dyn Listener> - к сожалению, ключевое слово dyn в настоящее время является необязательным. Оба Box<dyn Listener> и &Mouse реализуют Deref, со связанным типом Target, который реализует Listener. В случае &Mouse значение deref Target равно Mouse, но в случае Box<dyn Listener> это неизвестный объект, dyn Listener из неизвестного размера .

Чтобы собрать всю эту информацию, вы можете написать f так:

fn f<T, L>(_listener: &T)
where
    T: Deref<Target = L>,
    L: Listener + ?Sized
{
}

И вызывать ее из каждой функции следующим образом:

fn fbox(listener: &Box<dyn Listener>) {
    f(listener);
}

fn fref<L>(listener: &L)
where
    L: Listener
{
    f(&listener);
}

[ 1127] Другой, возможно, более простой способ взглянуть на это - отказаться от ограничения Deref и просто использовать обычные ссылки. Используйте Box::as_ref, чтобы превратить Box в ссылку, чтобы вызвать ее. Неограниченное ограничение ?Sized все еще необходимо для случая объекта черты, и все еще работает, так как значение всегда находится за указателем:

fn fbox(listener: &Box<dyn Listener>) {
    f(listener.as_ref());
}

fn fref<L>(listener: &L) where L: Listener {
    f(listener);
}

fn f<L>(_listener: &L)
where
    L: Listener + ?Sized
{
}
105
задан kevinarpe 5 November 2012 в 07:59
поделиться

8 ответов

Классы должны быть предназначены для наследования, чтобы иметь возможность использовать его в своих интересах. Наличие методов virtual по умолчанию означает, что каждая функция в классе может быть отключена и заменена другой, что на самом деле не очень хорошая вещь. Многие даже считают, что классы должны быть запечатаны по умолчанию. Виртуальные методы также могут иметь незначительное влияние на производительность. Однако вряд ли это будет основной причиной.

98
ответ дан 24 November 2019 в 03:58
поделиться

Чтобы подвести итог сказанному другими, есть несколько причин:

1- В C # есть много вещей в синтаксисе и семантике, которые приходят прямо из C ++. Тот факт, что методы, не являющиеся виртуальными по умолчанию в C ++, повлияли на C #.

2- Наличие каждого метода, виртуального по умолчанию, является проблемой производительности, поскольку каждый вызов метода должен использовать виртуальную таблицу объекта. Более того, это сильно ограничивает способность компилятора Just-In-Time встроять методы и выполнять другие виды оптимизации.

3- Самое главное, что если методы не являются виртуальными по умолчанию, вы можете гарантировать поведение ваших классов , Когда они виртуальные по умолчанию, такие как в Java, вы можете даже не гарантируют, что простой метод получения будет работать так, как задумано, потому что он может быть переопределен, чтобы делать что-либо в производном классе (конечно, вы можете и должны сделать метод и / или класс окончательным).

Можно задаться вопросом Как упоминал Зифре, почему язык C # не пошел дальше и не сделал классы запечатанными по умолчанию. Это часть всей дискуссии о проблемах наследования реализации, что является очень интересной темой.

12
ответ дан 24 November 2019 в 03:58
поделиться

На C # влияет C ++ (и более). C ++ не включает динамическую диспетчеризацию (виртуальные функции) по умолчанию. Одним из (хороших?) Аргументов для этого является вопрос: «Как часто вы реализуете классы, являющиеся членами класса hiearchy?». Другая причина, по которой следует избегать динамической отправки по умолчанию, - это объем памяти. Класс без виртуального указателя (vpointer), указывающего на виртуальную таблицу , конечно же, меньше соответствующего класса с включенным поздним связыванием.

Проблему производительности не так просто сказать «да» или « нет Причиной этого является компиляция Just In Time (JIT), которая является оптимизацией времени выполнения в C #.

Другой, похожий вопрос о « скорости виртуальных звонков .. »

9
ответ дан 24 November 2019 в 03:58
поделиться

Потому что слишком легко забыть, что метод может быть переопределен и не предназначен для этого. C # заставляет вас думать, прежде чем сделать его виртуальным. Я думаю, что это отличное дизайнерское решение. Некоторые люди (например, Джон Скит) даже сказали, что классы должны быть запечатаны по умолчанию.

17
ответ дан 24 November 2019 в 03:58
поделиться

Если бы все методы C # были виртуальными, тогда vtbl был бы намного больше.

Объекты C # имеют только виртуальные методы, если в классе определены виртуальные методы. Это правда, что все объекты имеют информацию о типе, которая включает эквивалент vtbl, но если виртуальные методы не определены, то будут присутствовать только методы базового объекта.

@Tom Hawtin: Вероятнее, точнее будет сказать, что C ++, C # и Java все из семейства языков C:)

2
ответ дан 24 November 2019 в 03:58
поделиться

Простая причина - затраты на проектирование и обслуживание в дополнение к затратам на производительность. Виртуальный метод имеет дополнительную стоимость по сравнению с не виртуальным методом, потому что разработчик класса должен планировать то, что происходит, когда метод переопределяется другим классом. Это имеет большое влияние, если вы ожидаете, что определенный метод обновит внутреннее состояние или будет иметь определенное поведение. Теперь вам нужно планировать, что произойдет, когда производный класс изменит это поведение. В такой ситуации гораздо сложнее написать надежный код.

С помощью не виртуального метода вы получаете полный контроль. Все, что идет не так, по вине первоначального автора. Код гораздо проще рассуждать.

5
ответ дан 24 November 2019 в 03:58
поделиться

Это, безусловно, не проблема производительности. Java-интерпретатор Sun использует для отправки тот же код ( invokevirtual bytecode), а HotSpot генерирует точно такой же код независимо от того, final или нет. Я считаю, что все объекты C # (но не структуры) имеют виртуальные методы, поэтому вам всегда потребуется идентификация класса vtbl / runtime. C # - это диалект "Java-подобных языков". Предполагать, что это исходит из C ++, не совсем честно.

Есть идея, что вы должны «создать наследство или запретить его». Это звучит как отличная идея, пока у вас есть серьезное экономическое обоснование, которое нужно быстро исправить. Возможно наследование от кода, который вы не контролируете.

0
ответ дан 24 November 2019 в 03:58
поделиться

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

Большинство оправданий читаются мне как старый аргумент «Если мы дадим вам силу, вы можете навредить себе». От программистов ?!

Мне кажется, что кодер, который не знал достаточно (или не имел достаточно времени), чтобы спроектировать свою библиотеку для наследования и / или расширяемости, - это кодер, который создал именно ту библиотеку, которую мне, вероятно, придется исправить или настроить. - именно та библиотека, в которой возможность переопределения может оказаться наиболее полезной.

Количество раз, которое мне приходилось писать уродливый, отчаянный обходной код (или отказываться от использования и откатывать собственное альтернативное решение), потому что я не могу переопределить, намного, намного превышает количество раз, которое я когда-либо был укушен (например, в Java) из-за того, что дизайнер мог не подумать, что я мог бы.

Отсутствие виртуальности по умолчанию усложняет мне жизнь.

ОБНОВЛЕНИЕ: Было указано [совершенно правильно], что я на самом деле не ответил на вопрос. Итак - и приношу свои извинения за опоздание ....

Я как бы хотел иметь возможность написать что-нибудь содержательное вроде «C # по умолчанию реализует методы как не виртуальные, потому что было принято плохое решение, которое ценило программы больше, чем программистов. ".(Я думаю, что это может быть несколько оправдано на основании некоторых других ответов на этот вопрос, таких как производительность (преждевременная оптимизация, кто угодно?) Или гарантия поведения классов.)

Однако я понимаю, что просто хочу заявить мое мнение, а не тот окончательный ответ, которого желает Stack Overflow. Конечно, я подумал, что на самом высоком уровне окончательный (но бесполезный) ответ таков:

Они не виртуальные по умолчанию, потому что разработчики языков должны были принять решение, и они выбрали это.

Теперь я предполагаю, что точную причину, по которой они приняли это решение, мы никогда не… ох, подожди! Стенограмма разговора!

Таким образом, может показаться, что ответы и комментарии здесь об опасностях переопределения API и необходимости явного проектирования для наследования находятся на правильном пути, но все они упускают важный временной аспект: основная забота Андерса заключалась в поддержании класса или неявный контракт API между версиями . И я думаю, что на самом деле он больше озабочен тем, чтобы позволить платформе .Net / C # изменяться в коде, чем беспокоиться об изменении пользовательского кода поверх платформы. (И его «прагматическая» точка зрения прямо противоположна моей, потому что он смотрит с другой стороны.)

(Но разве они не могли просто выбрать виртуальный по умолчанию, а затем добавить «окончательный» в кодовую базу? это не совсем то же самое ... и Андерс явно умнее меня, так что я позволю этому соврать.)

89
ответ дан 24 November 2019 в 03:58
поделиться