Страницы

Поиск по вопросам

суббота, 30 ноября 2019 г.

Причины использования DownCast

#c_sharp #ооп


Для чего применяется DownCast в C#? С UpCast всё понятно, но вот с этим вообще ничего
не понятно. Зачем его делать?
    


Ответы

Ответ 1



В хорошо спроектированной системе необходимость в понижающем приведении (downcast'е) минимальна. Однако, существуют случаи, при которых он нужен. Вот несколько примеров. Обычная причина — слабость системы типов. Представьте себе, что вы хотите запрограммировать класс с выделенным экземпляром. Но этот класс можно модифицировать, унаследовавшись от него. Это приводит к примерно такому коду: namespace System.Windows { class Application { static public Application Current { get; private set; } public Application() { Current = this; } } } namespace MyCoolApp { class App : Application { public MainVM MainVM { get; private set; } } class MyControl { // ... var mainVM = ((App)Application.Current).MainVM; } } Знакомый код, не правда ли? Такое приходится писать потому, что система типов не может выразить требование, чтобы Current было актуального типа, чтобы можно было написать App.Current. К аналогичной проблеме приводит попытка описать класс, который должен иметь оператор сравнения с другим экземпляром того же типа. Другой валидный случай использования понижающего приведения — виртуализация по типу объекта вне иерархии классов. Представьте себе, что вам нужно сделать разное поведение кода для разных классов в одном дереве наследования. Обычно это решается при помощи виртуальных функций, но что, если функциональность не имеет отношения к самому объекту? Допустим, у вас есть иерархия типов геометрических фигур, и вы хотите добавить «сбоку» метод печати на консоль. Вносить этот метод абстрактным в базовый класс нет смысла, потому что вывод на консоль не имеет прямого отношения к геометрической фигуре. Поэтому приходится писать сторонний метод, выяснять runtime-тип объекта и приводить к нему. Ещё одно место, где приходится заниматься подобным — паттерн Visitor с крайне уродливой классической имплементацией (без dynamic). Это, наверное, тоже можно считать слабостью системы типов, по крайней мере в будущих версиях языка это должно решаться другими средствами. Ещё один случай — существующий код, к «общему знаменателю» которого приходится приводить свой код. Например, много кода опирается на интерфейс INotifyPropertyChanged, в котором значения свойств представляются наиболее широким типом — object. А значит, для работы с реальным значением нужен downcast. По сути, можно было бы отказаться от нетипизированного INotifyPropertyChanged, и пользоваться типизированным IObservable для каждого свойства отдельно, но для начала придётся уговорить компанию Microsoft переписать WPF! Ну и последний случай, который я вижу — необходимость «залатать» дыру, которую невозможно залатать правильными средствами из-за внешних условий. Например, нужно бы переделать иерархию классов, но дедлайн завтра. Или код производного класса поставляется другим отделом. Здесь может оказаться необходимым нарушить LSP и сделать что-то по-другому для конкретного порождённого класса.

Ответ 2



Ну вообще это много где может быть полезно. Для такого приведения даже существует специальный оператор: Foo foo = (Foo)someObject; Примером того, когда это может быть полезно является случай работы с необобщенными коллекциями: List lst = new List() { new Foo() }; Foo foo = (Foo)lst[0]; Но вообще необходимо отметить, что подобное приведение на самом деле зачастую является не очень хорошей практикой, так как чревато ошибками в рантайме. В частности, если говорить о приведенном мной примере использование этого приема является необходимым злом, поскольку по тем или иным причинам в версиях .NET < 2.0 отсутствовали обобщенные коллекции, а потому по факту все подобные коллекции хранили ссылки на object со всеми вытекающими отсюда проблемами. В случае, если lst[0] имеет какой-то другой тип, будет сгенерировано исключение. З.Ы. кстати, не путаете ли вы Downcast (приведение предка к наследнику) и Upcast (приведение наследника к предку)?

Ответ 3



Наиболее распространённая ситуация, когда мы точно знаем тип объекта, но по какой-то причине получаем его с типом object. Стандартные ситуации: В обработчики событий принято передавать object sender, но в большинстве случаев, подписываясь на событие, например, в winforms приложении, мы точно знаем, кто будет sender'ом. Например, если мы знаем, что это один из TextBox'ов в массиве, то приводим его к TextBox'у и делаем то, что нам нужно. Интерфейс IClonable. Метод Clone возвращает объект того же типа, что исходный (ну, если он этого не делает, то надо кому-то руки поотрывать), однако, объявлен от с типом object. Тут без приведения вообще никак. Устаревшие коллекции (которые не generic'и) всё делают через object, но использующий их, вероятно, складывает в них что-то конкретное, а не кашу из всего подряд.

Ответ 4



Причины, на самом деле, достаточно просты. Нам необходимо получить доступ к определенному поведению, которое реализуется только в классе-наследнике. Текущая архитектура не позволяет это сделать с помощью виртуальных методов. Получается, что других вариантов кроме как специализировать тип у нас не остается. Для более детального ответа необходим пример use-case, который вызвал данный вопрос.

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

Отправить комментарий