Понижение означает преобразование из базового класса (или интерфейса) в подкласс или листовой класс.
Примером снижения может быть, если вы приведете System.Object
к другому типу.
Даункастинг непопулярен, может быть, пахнет кодом: доктрина объектно-ориентированного подхода предпочитает, например, определять и вызывать виртуальные или абстрактные методы, а не унижать.
- Какие, если таковые имеются, хорошие и правильные варианты использования для даункинг? То есть, при каких обстоятельствах уместно писать код, который преуменьшает значение?
- Если ваш ответ «нет», то почему языком поддерживается пониженная версия?
c#
object-oriented
ChrisW
источник
источник
ToString
); другой пример - «контейнеры Java», потому что Java не поддерживает обобщенные элементы (что делает C #). stackoverflow.com/questions/1524197/downcast-and-upcast говорит , что понижающее приведение есть , но без примеров , когда это уместно .before Java had support for generics
не такbecause Java doesn't support generics
. И Амон в значительной степени дал вам ответ - когда система типов, с которой вы работаете, слишком ограничена, и вы не можете ее изменить.Ответы:
Вот некоторые правильные способы использования downcasting.
И я с уважением категорически не согласен с другими здесь, которые говорят, что использование downcast - это определенно запах кода, потому что я считаю, что нет другого разумного способа решения соответствующих проблем.
Equals
:Clone
:Stream.EndRead/Write
:Если вы собираетесь сказать, что это запахи кода, вам нужно предоставить более эффективные решения для них (даже если их избежать из-за неудобств). Я не верю, что существуют лучшие решения.
источник
object.Equals
который вполне соответствует этому описанию (попробуйте правильно предоставить контракт на равенство в суперклассе, который позволяет подклассам правильно его реализовать - это абсолютно нетривиальный). То, что как прикладные программисты мы не можем решить это (в некоторых случаях мы можем избежать этого), это отдельная тема.Object.Equals is horrid. Equality computation in C# is completely messed up
(комментарий Эрика к его ответу). Забавно, что вы не хотите пересматривать свое мнение, даже если один из ведущих разработчиков языка говорит вам, что вы не правы.Я не согласен. Даункинг чрезвычайно популярен ; огромное количество реальных программ содержат один или несколько downcast. И это не может быть запах кода. Это определенно кодовый запах. Вот почему операция даункейтинга должна быть указана в тексте программы . Это так, что вы можете легче заметить запах и уделить внимание проверке кода.
В любых обстоятельствах, когда:
Если вы можете дешево реорганизовать программу, чтобы компилятор мог определить тип среды выполнения или реорганизовать программу, чтобы вам не требовались возможности более производного типа, тогда сделайте это. Даункасты были добавлены к языку для тех обстоятельств, когда трудно и дорого рефакторинг программы.
C # был изобретен прагматичными программистами, у которых есть работа, для прагматичных программистов, у которых есть работа. Дизайнеры C # не являются пуристами OO. И система типов C # не идеальна; по своей конструкции он недооценивает ограничения, которые могут быть наложены на тип переменной во время выполнения.
Кроме того, downcasting очень безопасен в C #. У нас есть надежная гарантия, что downcast будет проверен во время выполнения, и если его невозможно будет проверить, тогда программа сделает правильные вещи и вылетит. Это замечательно; это означает, что если ваше 100% правильное понимание семантики типов окажется правильным на 99,99%, то ваша программа работает 99,99% времени и в остальное время вылетает, а не ведет себя непредсказуемо и портит пользовательские данные 0,01% времени ,
УПРАЖНЕНИЕ: есть по крайней мере один способ произвести даункаст в C # без использования явного оператора приведения. Можете ли вы вспомнить какие-либо такие сценарии? Поскольку это также потенциальные запахи кода, как вы думаете, какие конструктивные факторы были заложены в разработку функции, которая могла бы привести к падению с понижением при отсутствии явного приведения в коде?
источник
Object.Equals
это ужасно . Вычисление равенства в C # полностью испорчено , слишком запутано, чтобы объяснить это в комментарии. Есть так много способов представить равенство; очень легко сделать их непоследовательными; это очень легко, фактически требуется нарушить свойства, требуемые от оператора равенства (симметрия, рефлексивность и транзитивность). Если бы я сегодня проектировал систему типов с нуля, не было быEquals
(илиGetHashcode
илиToString
)Object
вообще.Обработчики событий обычно имеют подпись
MethodName(object sender, EventArgs e)
. В некоторых случаях можно обрабатывать событие, не обращая внимания на то, что это за типsender
, или даже вообще не использоватьsender
, но в другихsender
необходимо привести к более конкретному типу для обработки события.Например, у вас может быть два
TextBox
s, и вы хотите использовать один делегат для обработки события от каждого из них. Затем вам нужно привестиsender
кTextBox
так, чтобы вы могли получить доступ к необходимым свойствам для обработки события:Да, в этом примере вы можете создать свой собственный подкласс,
TextBox
который делал это внутренне. Не всегда практично или возможно, хотя.источник
TextChanged
Событие является членомControl
- Интересно , почемуsender
это не из типаControl
вместо типаobject
? Также мне интересно, могла ли (теоретически) структура переопределить событие для каждого подкласса (чтобы, например,TextBox
могла иметь новую версию события, используяTextBox
в качестве типа отправителя) ... которая могла бы (или не могла) требовать понижения ( кTextBox
) внутриTextBox
реализации (для реализации нового типа события), но не будет требовать понижения в обработчике события кода приложения.TextBox sender
а неobject sender
без чрезмерного усложнения кода, я уверен, что это было бы сделано.Вам нужно понизить рейтинг, когда что-то дает вам супертип, и вам нужно обращаться с ним по-разному в зависимости от подтипа.
Нет, это не пахнет хорошо. В идеальном мире
Donation
был бы абстрактный метод,getValue
и каждый реализующий подтип имел бы соответствующую реализацию.Но что, если эти классы взяты из библиотеки, которую вы не можете или не хотите менять? Тогда другого варианта нет.
источник
dynamic
ключевое слово, которое достигает того же результата.void accept(IDonationVisitor visitor)
, который его подклассы реализуют, вызывая определенные (например, перегруженные) методыIDonationVisitor
.dynamic
ключевое слово.dynamic
все еще удручает , только медленнее.Чтобы добавить к ответу Эрика Липперта, так как я не могу комментировать ...
Вы можете часто избегать снижения рейтинга путем рефакторинга API для использования обобщений. Но дженерики не были добавлены в язык до версии 2.0 . Поэтому, даже если ваш собственный код не нуждается в поддержке версий на древнем языке, вы можете использовать устаревшие классы, которые не параметризованы. Например, некоторый класс может быть определен так, чтобы нести полезную нагрузку типа
Object
, которую вы вынуждены привести к типу, который, как вы знаете, на самом деле является.источник
Существует компромисс между статическими и динамически типизированными языками. Статическая типизация дает компилятору много информации, чтобы иметь возможность давать довольно сильные гарантии безопасности (частей) программ. Однако это обходится дорого, потому что вам нужно не только знать, что ваша программа корректна, но вы должны написать соответствующий код, чтобы убедить компилятор, что это так. Другими словами, предъявлять претензии легче, чем доказывать их.
Существуют «небезопасные» конструкции, которые помогут вам сделать недоказанные утверждения для компилятора. Например, безусловные вызовы
Nullable.Value
, безусловные откаты,dynamic
объекты и т. Д. Они позволяют вам утверждать утверждение («Я утверждаю, что объектa
- этоString
, если я ошибаюсь, выбрасываюInvalidCastException
») без необходимости доказывать это. Это может быть полезно в тех случаях, когда доказать это значительно сложнее, чем стоит.Злоупотребление этим рискованно, именно поэтому явная нисходящая нотация существует и является обязательной; это синтаксическая соль, предназначенная для привлечения внимания к небезопасной операции. Язык мог бы быть реализован с неявным понижением (где выводимый тип однозначен), но это скрыло бы эту небезопасную операцию, что нежелательно.
источник
Даункинг считается плохим по нескольким причинам. В первую очередь я думаю, потому что это анти-ООП.
ООП действительно понравилось бы, если бы вам никогда не приходилось опускать руки, потому что его смысл - в том, что полиморфизм означает, что вам не нужно идти
ты просто делаешь
и код автоматически делает правильные вещи.
Есть несколько «веских» причин не опускать руки:
Но альтернатива понижению в некоторых сценариях может быть довольно уродливой.
Классическим примером является обработка сообщений, когда вы не хотите добавлять функцию обработки к объекту, а оставляете ее в обработчике сообщений. Затем у меня есть тонна
MessageBaseClass
массива для обработки, но для корректной обработки мне также нужен каждый из них по подтипу.Вы можете использовать Double Dispatch, чтобы обойти проблему ( https://en.wikipedia.org/wiki/Double_dispatch ) ... Но это также имеет свои проблемы. Или вы можете просто унынуть из-за простого кода, рискуя этими вескими причинами.
Но это было до того, как были изобретены дженерики. Теперь вы можете избежать даункинга, указав тип, где детали указаны позже.
MessageProccesor<T>
может варьировать параметры метода и возвращаемые значения указанным типом, но все же предоставлять общие функциональные возможностиКонечно, никто не заставляет вас писать ООП-код, и существует множество языковых функций, которые предоставляются, но не одобряются, например рефлексия.
источник
static_cast
вместоdynamic_cast
.lParam
к чему-то конкретному Я не уверен, что это хороший пример C #, хотя.static_cast
всегда быстро ... но не гарантируется, что он не потерпит неудачу неопределенными способами, если вы ошиблись, а тип не тот, который вы ожидаете.В дополнение ко всему, что уже было сказано, представьте свойство Tag, имеющее тип object. Он позволяет вам сохранить выбранный вами объект в другом объекте для дальнейшего использования по мере необходимости. Вам нужно понизить это свойство.
В общем, неиспользование чего-либо до сегодняшнего дня не всегда является признаком чего-то бесполезного ;-)
источник
Control
(вместо использованияobject Tag
свойства) состоит в созданииDictionary<Control, string>
метки для хранения метки для каждого элемента управления.Tag
это открытое свойство класса платформы .Net, которое показывает, что вы (более или менее) должны его использовать.System.Collections.ArrayList
) или иным образом осуждается (например,IEnumerator.Reset
).Самое распространенное использование уныния в моей работе связано с тем, что какой-то идиот нарушает принцип замещения Лискова.
Представьте, что в какой-то третьей стороне есть интерфейс.
И 2 класса, которые это реализуют.
Bar
иQux
.Bar
хорошо себя ведетНо
Qux
плохо себя ведет.Ну ... теперь у меня нет выбора. Я должен напечатать check и (возможно) downcast, чтобы избежать остановки моей программы.
источник
var qux = foo as Qux;
.Другой реальный пример - WPF
VisualTreeHelper
. Он использует downcast для приведенияDependencyObject
кVisual
иVisual3D
. Например, VisualTreeHelper.GetParent . Понижение происходит в VisualTreeUtils.AsVisualHelper :источник