Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1] 2  Все   Вниз
  Печать  
Автор Тема: Любопытная фича наследования в C#/CLR  (Прочитано 36194 раз)
0 Пользователей и 1 Гость смотрят эту тему.
Вад
Команда клуба

ru
Offline Offline
Пол: Мужской

« : 21-07-2010 21:23 » 

Прочитал в переводе любопытную статью Эрика Липперта на тему того, как можно отстрелить себе ногу с помощью C# и модификации класса в середине иерархии.

Если вкратце, то ситуация, возможно, несколько неординарная: если есть 3 класса иерархии A->B->C, и каждый находится в своей сборке, то виртуальные вызовы в классе C могут повести себя несколько неожиданным образом после добавления override в B и его перекомпиляции (без пересборки C).

В общем, подробно по ссылке:

http://blogs.msdn.com/b/ruericlippert/archive/2010/07/21/putting-a-base-in-the-middle.aspx

От себя хотелось бы отметить, что был неприятно удивлён длинным и сбивчивым объяснением всех причин происходящего. По-моему, налицо нарушение принципа "Make everything as simple as possible, but not simpler".

Это уже не первый такой хак в C#, и чем дальше - тем больше C# по сложности реализации начинает напоминать C++, в плохом смысле этого слова.
« Последнее редактирование: 21-07-2010 21:25 от Вад » Записан
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #1 : 22-07-2010 05:46 » 

IMHO вполне естественное поведение. Компилятор сделал все, что мог на момент компиляции. Более динамическое решение заставило бы заниматься интерпретацией вызовов во время исполнения и ударило бы по производительности.

Для критичных приложений, когда изменение класса в середине цепочки наследования может повлечь серьезные проблемы, вполне можно защититься версионированием сборок. Оно ведь в том числе и для подобных ситуаций предназначено.
Записан

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #2 : 22-07-2010 07:37 » 

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

Проблема не в языке, а в практике поставки сборок вместо исходников - когда имеется лишь dll с C. Вообще рекомендация перекомпиляции всего, что зависит от места внесения изменений, по-моему, разумна.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Джон
просто
Администратор

de
Offline Offline
Пол: Мужской

« Ответ #3 : 22-07-2010 07:50 » 

IMHO проблема высосана из пальца. Charli зависит от Bravo и ессно должна быть перекомпилирована при изменении Bravo. Иначе таких проблем можно "понапридумать" туеву хучу. Например, сделать наоборот. Сначала собрать Charli с существующим в Bravo методом М(), а потом удалить М() из Bravo. В итоге долго удивляться почему программа вылетает с ошибкой "не найден метод Bravo.M()". Или вобще наследовать Bravo от Delta.

Это скорей из серии: "А я таки придумал ситуацию, в которой эта падла не работает!"

Не ну серьёзно, кто-то может предложить (или предположить) реально ситуацию, необходимости ТАКОЙ корявой архитектуры проекта? Да ещё в добавок, чтобы dll были от разных производителей.
« Последнее редактирование: 22-07-2010 07:52 от Джон » Записан

Я вам что? Дурак? По выходным и праздникам на работе работать. По выходным и праздникам я работаю дома.
"Just because the language allows you to do something does not mean that it’s the correct thing to do." Trey Nash
"Physics is like sex: sure, it may give some practical results, but that's not why we do it." Richard P. Feynman
"All science is either physics or stamp collecting." Ernest Rutherford
"Wer will, findet Wege, wer nicht will, findet Gründe."
Джон
просто
Администратор

de
Offline Offline
Пол: Мужской

« Ответ #4 : 22-07-2010 07:56 » 

Offtopic:
В последнее время почему-то всё чаще и чаще сталкиваюсь с такими явлениями. Наверное жара влияет. Даже добавил в подпись, как мне кажется, слова Трея Нэша в качестве ответа на подобные "провокации".
Поставлю в угол.
Записан

Я вам что? Дурак? По выходным и праздникам на работе работать. По выходным и праздникам я работаю дома.
"Just because the language allows you to do something does not mean that it’s the correct thing to do." Trey Nash
"Physics is like sex: sure, it may give some practical results, but that's not why we do it." Richard P. Feynman
"All science is either physics or stamp collecting." Ernest Rutherford
"Wer will, findet Wege, wer nicht will, findet Gründe."
Вад
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #5 : 22-07-2010 07:59 » 

Про разумность перекомпиляции я не возражаю Улыбаюсь Я возражаю против хитрых доводов типа "ну, у нас вот тут так нечаянно получилось, что такое вообще работает. Но ведь это даже к лучшему, так код хакнуть не смогут, и потом, он же не падает, правда?"
В этом смысле мне претит, что перекомпиляцию мне сам язык не навязывает.
И, скажем, когда библиотеки-зависимости несовместимы по платформе с приложением (я тут как-то писал про грабли с .NET Win32 vs x64), не навязывает - в compile-time. Все проблемы нужно выяснять тестированием.
Записан
Вад
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #6 : 22-07-2010 08:02 » 

Джон, я не спорю, архитектура для такого случая наверняка должна быть корявой Улыбаюсь Просто у данного товарища в блоге подобный стиль разведения руками, почему так получилось, несколько склоняет меня к мысли, что компилятор C# тоже писался довольно коряво.
Записан
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #7 : 22-07-2010 08:13 » 

В этом смысле мне претит, что перекомпиляцию мне сам язык не навязывает.

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

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Джон
просто
Администратор

de
Offline Offline
Пол: Мужской

« Ответ #8 : 22-07-2010 08:44 » 

несколько склоняет меня к мысли, что компилятор C# тоже писался довольно коряво.

Я ни в коем разе не пытаюсь доказать обратное. Просто я подхожу к подобным "проблемам" практически: "а могу ли я наступить?", "а есть ли у меня уже нечто подобное?", чтобы исправить, предусмотреть и тд. И если проблема действительно имеет место быть, то честь и хвала нашедшему. Остальное - пустая трата времени.
Записан

Я вам что? Дурак? По выходным и праздникам на работе работать. По выходным и праздникам я работаю дома.
"Just because the language allows you to do something does not mean that it’s the correct thing to do." Trey Nash
"Physics is like sex: sure, it may give some practical results, but that's not why we do it." Richard P. Feynman
"All science is either physics or stamp collecting." Ernest Rutherford
"Wer will, findet Wege, wer nicht will, findet Gründe."
Вад
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #9 : 22-07-2010 08:46 » 

Dale, под навязыванием я понимаю то, что высокоуровневый язык это самое версионирование должен сам уметь делать. В том смысле, чтобы не допускать неопределённое поведение в случае изменений посредине. От нерадивости же не спасёт ничего Улыбаюсь Защита от изобретательного дурака стоит слишком дорого.

Речь же в целом была не столько о том, что язык позволяет мне делать, сколько о том, почему он мне это позволяет. Мотивация разработчиков неубедительная. Грабли вроде этих, но в более вменяемой ситуации (как пресловутая разрядность платформ), могут стоить непростительно много времени. И сколько их там ещё, таких граблей, неизвестно.
Записан
Вад
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #10 : 22-07-2010 08:47 » new

несколько склоняет меня к мысли, что компилятор C# тоже писался довольно коряво.

Я ни в коем разе не пытаюсь доказать обратное. Просто я подхожу к подобным "проблемам" практически: "а могу ли я наступить?", "а есть ли у меня уже нечто подобное?", чтобы исправить, предусмотреть и тд. И если проблема действительно имеет место быть, то честь и хвала нашедшему. Остальное - пустая трата времени.

В данном случае я просто привёл любопытный пример граблей, безотносительно к вероятности на них наступить. Всё-таки, бывает интересно, как язык изнутри устроен, и каким образом рассуждают его создатели.
Записан
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #11 : 22-07-2010 08:57 » 

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

Я не вполне понял... Версионирование сборок - это важнейшее средство среды .NET. Версионироваться может любая управляемая сборка, в том числе и написанная на C#. Зачем дополнительно выносить на уровень языка то, что и так подерживается? (Абстрактный случай реализации C# вне среды .NET я не рассматриваю).
Записан

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Вад
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #12 : 22-07-2010 09:00 » 

Dale, так может или делается? Раз выверты, подобные описанному в статье, возможны - я для себя заключаю, что как-то не до конца оно важнейшее.
Записан
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #13 : 22-07-2010 09:19 » 

Dale, так может или делается?

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

Что касается вынесения подобных аспектов на уровень языка... Вернемся к примеру из статьи: имеются классы А, В, С, каждый в отдельной сборке, иерархия наследования такова: А <- В <- С. При написании исходников класса С мы пишем:
Код:
class B : C ... 

Но отдельно взятый текст на C# абстрактен, как сферический конь в вакууме. Когда дело дойдет до компиляции С, потребуется явно указать, откуда взять метаданные В. Как это будет сделано технически, не суть важно: вручную через командную строку компиляции, указанием Reference в IDE Visual Studio или настройкой make-файла. В любом случае суть одна: исходный файл на C# не содержит всей необходимой информации для построения сборки. Дело за малым: указать не только имя сборки, содержащей класс В, но и ее версию. Хлопот минимум, проблема исчезает.
Записан

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Вад
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #14 : 22-07-2010 10:35 » 

Dale, это хорошо, когда таких ритуальных знаний, вроде версионирования, немного. Скажем, против версионирования самого по себе я ничего не имею: нужно делать вручную - запомнили, делаем вручную, через References и т.п.

Лишь бы этих ритуальных знаний не становилось больше. Когда команда делает небольшой проект и сидит с чек-листом на 54 метра, проверяя, не забыли ли они чего из best-practices - это плохо. Я утрирую, но тенденция в том, что как только число побочных действий по контролю качества становится слишком большим, нужны инструментальные средства с нативной поддержкой, а не ручные практики. Потому что придёт какой-нибудь джуниор, прочитает чек-лист по диагонали, где-то чего-то недопоймёт - и вот они, проблемы.

Да, каюсь, я говорил "язык", подразумевая скорее функции IDE: контроль целостности проекта и т.п. должны делать инструменты, это не часть синтаксиса языка. C# для меня почти неотделим от своей среды (Mono в расчёт не берём), отсюда неточность. Вот именно этот контроль и кажется мне неполным: если точнее, именно некоторая зыбкость в логике реализации инструментальных средств (включая компилятор) меня и раздражает.
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #15 : 22-07-2010 10:35 » 

Цитата: Вад
Всё-таки, бывает интересно, как язык изнутри устроен, и каким образом рассуждают его создатели.
Тогда читай Н.Вирта про Oberon. Полное душевное спокойствие обеспечено, хотя кровь стынет в жилах от такой языковой паталогоанатомии Улыбаюсь
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #16 : 22-07-2010 10:50 » 

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

Конечно, неявная генерация кода тоже имеет недостатки. Но, с другой стороны, миримся ведь мы с неявными конструкторами.
Записан

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #17 : 22-07-2010 11:00 » 

Dale, твоё предложение не годится для случая, описанного по ссылке. Если ты в B нужный метод таки переопределишь, пометишь невиртуальным, а ещё и закрытым, твоя схема решения приведёт к ошибке времени исполнения CLR, а в общем случае - к неявным побочным эффектам, нарушающим семантику кода.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #18 : 22-07-2010 11:15 » 

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

Если я переопределю метод и одновременно объявлю его невиртуальным, это всего лишь скроет одноименный виртуальный метод, но не сделает его недоступным через таблицу виртуальных методов (типичное действие модификатора new). Тоже не должно вызвать проблем, ведь методы не по именам вызываются. Аналогично и с его закрытием.
Записан

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #19 : 22-07-2010 12:02 » 

Dale, там же в тексте написано, что поиск метода осуществляется по сигнатуре. Не может быть двух методов с одинаковой сигнатурой - чтобы один бы "тайным", а другой - определённым пользователем. Опять будут вылезать левые побочные эффекты.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #20 : 22-07-2010 12:14 » 

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

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

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #21 : 22-07-2010 13:14 » 

Причём тут доверие к тексту?

Цитата: Dale
Если я переопределю метод и одновременно объявлю его невиртуальным, это всего лишь скроет одноименный виртуальный метод, но не сделает его недоступным через таблицу виртуальных методов (типичное действие модификатора new).
Что действительно не вызывает доверия, так это все эти переопределения с new и явная реализация интерфейсов. Всё, что порождает зависимость поведения объекта от типа переменной, равно как и "невиртуальность" методов. Но последняя хотя бы оправдана уменьшением расхода памяти и повышением скорости исполнения.
« Последнее редактирование: 22-07-2010 13:17 от Dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #22 : 22-07-2010 13:23 » 

Всё, что порождает зависимость поведения объекта от типа переменной, равно как и "невиртуальность" методов.

Так ведь зависимость поведения объекта от типа и реализуется через виртуальность. Если не вызывают доверия виртуальность и невиртуальность одновременно, что же тогда останется?
Записан

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #23 : 22-07-2010 14:14 » 

Цитата: Dale
Так ведь зависимость поведения объекта от типа и реализуется через виртуальность.
Ты путаешь тип переменной и тип значения - это разные вещи.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #24 : 22-07-2010 17:03 » 

Ты путаешь тип переменной и тип значения - это разные вещи.

Если честно, я их не путаю. Похоже, я с ними вовсе не знаком (по крайней мере, в рамках C# и прочих сильно типизированных языков).

Я знаю единственное понятие - тип. Переменная имеет тип, значение имеет тип. Если эти типы совпадают, переменной можно присвоить значение. Если нет, начинается долгая сказка про приведения типов. О том, что это принципиально разные сущности, я впервые слышу. Или я что-то пропустил, или тут неувязка с терминами.
Записан

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #25 : 22-07-2010 18:28 » 

Dale, тобою сказанное справедливо для языков, где типы не имеют между собой отношения AKO (a kind of), в просторечии нередко именуемого наследованием. В таких языках любой тип соответствует лишь сам себе: A и B всегда различны. Если типы находятся в отношении AKO между собой, между ними возникает несимметричное соответствие "A - это B", но не "B - это A". Это ты и сам знаешь.

Поэтому без приведения типов допустимы присваивания:
Код:
A a; B b;
b = a;

Можно и так сказать, что у значения a одновременно несколько типов: A и B. Но у переменной a тип всегда только один - A.

В случае невиртуальных методов поведение зависит от типа переменной, а не от типа значения:
Код:
class A { public void M() { Console.WriteLine("A"); } }
class B: A { public new void M() { Console.WriteLine("B"); } }

B b = new B();
b.M(); // B
A a = b;
a.M(); // A
что на мой взгляд не соответствует очевидностям реального мира. Если белого вымазать сажей, он негром не станет. Понятно, почему так сделано - историческое наследие компиляторов и т.п. Но использовать это для описания информационных моделей, мягко говоря, неудобно.

В случае виртуальных методов всё нормально - поведение зависит только от типа значения, от "содержания" объекта, а не от "формы" того котейнера (переменной) в которой объект "хранится".
Код:
class A { public virtual void M() { Console.WriteLine("A"); } }
class B: A { public override void M() { Console.WriteLine("B"); } }

B b = new B();
b.M(); // B
A a = b;
a.M(); // B

Когда же в язык вставляется что-то вроде "явной реализации интерфейсов", всё возвращается к семантическим странностям невиртуальных методов.
Код:
interface A { void M(); }
interface B { void M(); }
class C: A, B
{
  void A.M() { Console.WriteLine("A.M"); }
  void B.M() { Console.WriteLine("B.M"); }
  public void virtual M() { Console.WriteLine("C.M"); }
}

C c = new C();
c.M(); // C.M
A a = c;
a.M(); // A.M
B b = c;
b.M(); // B.M
Спрашивается, нафига? Для моделирования в программе двуликих Янусов или волков в овечьей шкуре? Фильм "The thing"...

Я точно не знаю, но подозреваю, что это было сделано для реализации синтаксического сахара типа foreach, using, lock и т.п. избыточных (хотя и удобных) синтаксических конструкций. Чтобы не дай бог методы реализации каких-нибудь глубоко системных интерфейсов не торчали из пользовательких объектов.

Тогда интерфейсы в языке - это не только интерфейсы, а способ искуственного ослабления типизации объекта в строго типизированном языке. Ввести нетипизированные переменные (или переменные с перечнем типов) религия не позволяла (до последнего времени), поэтому добавлялись такие костыли. Теперь нетипизированные переменные ввели, а костыли остаются "для обратной совместимости".
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #26 : 22-07-2010 20:46 » 

Dale, тобою сказанное справедливо для языков, где типы не имеют между собой отношения AKO (a kind of), в просторечии нередко именуемого наследованием. В таких языках любой тип соответствует лишь сам себе: A и B всегда различны. Если типы находятся в отношении AKO между собой, между ними возникает несимметричное соответствие "A - это B", но не "B - это A". Это ты и сам знаешь.

Вообще-то я знаю немного другое. Ну, или думаю, что знаю.

Если тип А является производным от типа В, то в этом случае ссылка на экземпляр типа А может быть неявно приведена к ссылке на экземпляр типа B. Или, другими словами, если где-то ожидается ссылка на В, в ее роли вполне может выступить ссылка на А. Не более того.

Поэтому без приведения типов допустимы присваивания:
Код:
A a; B b;
b = a;

Если приведенный фрагмент написан на C#, в котором объектные переменные имеют исключительно ссылочную семантику, то я его интерпретирую несколько иначе: ссылка на экземпляр дочернего объекта А неявно преобразуется к типу ссылки на базовый класс В, после чего следует присваивание. Вроде бы не противоречит предыдущему пункту. То есть (неявное) приведение типов все-таки имеет место, хотя мы об этом и не просили. В принципе могли бы написать:
Код:
b = (b)a;
, суть бы от этого ничуть не изменилась.

Можно и так сказать, что у значения a одновременно несколько типов: A и B. Но у переменной a тип всегда только один - A.

Возможно, я повторюсь... С учетом того, что реальное значение a как таковое является ссылкой на экземпляр A, не лучше ли сказать, что эта ссылка может быть неявно преобразована к ссылке на базовый тип B? Реальный тип у экземпляра один, и этот тип A. Если мы спросим посредством рефлексии у переменной b ее тип, она нам сообщит, что на самом деле она урожденная A. Точно так же подлог вскроется при сериализации b.

У любой переменной тип один-единственный, а дискуссия о значении объектной переменной в рамках языка, в котором объектные переменные в принципе не могут иметь семантику значения, по-моему, только запутывает дело.
« Последнее редактирование: 22-07-2010 21:06 от Dale » Записан

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #27 : 23-07-2010 08:12 » 

Цитата: Dale
Если тип А является производным от типа В, то в этом случае ссылка на экземпляр типа А может быть неявно приведена к ссылке на экземпляр типа B. Или, другими словами, если где-то ожидается ссылка на В, в ее роли вполне может выступить ссылка на А. Не более того.
Это техническая сторона дела, скрытая от программиста, а не семантика написанного кода. Замечательно, когда программист это знает, но это необязательное знание. Конечно, можно нарываться на неприятности из-за "дырявых абстракций" и незнания устройства этих абстракций - мир несовершенен. Но можно и не нарваться. Например, я за свою 8-летнюю практику работы с C# лишь один раз наткнулся на разницу между оператором == и методом Equal.

Цитата: Dale
С учетом того, что реальное значение a как таковое является ссылкой на экземпляр A, не лучше ли сказать, что эта ссылка может быть неявно преобразована к ссылке на базовый тип B? Реальный тип у экземпляра один, и этот тип A. Если мы спросим посредством рефлексии у переменной b ее тип, она нам сообщит, что на самом деле она урожденная A. Точно так же подлог вскроется при сериализации b.
Эта интерпретация верна, но какой от неё прок, если ссылки в языке явным образом не выделены на уровне синтаксиса? Это годится для C++. Справедливее даже будет сказать, что нессылочные типы в C# - скорее исключение, имеющее свои особенности в пользу повышения производительности.

В конце концов можно установить соответствие: я говорю "значение" - ты говоришь "значение", я говорю "переменная", ты говоришь "ссылка". Чисто технически с тех пор, как появились языки без прямой адресации в исходном коде, переменная - это символическое обозначение адреса памяти, где хранится значение. С каких-то пор, но тоже давних (по меньшей мере ещё в первом Pascal), стало возможным один и тот же адрес обозначать несколькими разными символами в разных частях программы. В этом ключе название "переменная" более традиционно. А под "ссылкой" в том же C# скорее понимается некий аналог указателя, но в C# нет семантики "адреса", "арифметики адресов" (хотя и присутствует сравнение ссылок) и т.п. вещей, которая присутствует в языках с явным выделением типизированных указателей.

Цитата: Dale
У любой переменной тип один-единственный
Согласен, если это типизированная переменная. Нетипизированная переменная (начиная с .NET 3.0) уже требует оговорок. После её инициализации тип у неё один, определяемый типом значения, - до её уничтожения. Но в разных периодах существования этой переменной тип может быть разным. В какой-то мере это аналог параметризированного решения/шаблона (template).

Цитата: Dale
дискуссия о значении объектной переменной в рамках языка, в котором объектные переменные в принципе не могут иметь семантику значения, по-моему, только запутывает дело.
Вот эту фразу "переменные в принципе не могут иметь семантику значения" я не понял. Естественно, переменная - это не значение, это лишь способ обращения к значению. Причём в любом языке программирования. Смысловая разница между переменной и значением вообще ещё в математике возникла.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dale
Блюзмен
Команда клуба

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #28 : 23-07-2010 08:47 » 

Вот эту фразу "переменные в принципе не могут иметь семантику значения" я не понял. Естественно, переменная - это не значение, это лишь способ обращения к значению.

Вот что имелось в виду. Поскольку объектная переменная имеет семантику ссылки, а не значения, в результате конструкции
Цитата
A a; B b;
b = a;
переменная b на самом деле ссылается на тот же экземпляр, что и a. В случае семантики значения мы бы имели копирование значения a в b, после чего это были бы совершенно разные объекты. Думаю, для многих новичков является сюрпризом, когда они обнаруживают, что при изменении a одновременно меняется и b.

В процессе присваивания с самим первоначальным экземпляром ничего не происходит. Поэтому говорить о том, что у данного экземпляра появился еще один тип, не совсем корректно. Появилась лишь ссылка b, значением которой является ссылка на a, преобразованная к типу "ссылка на b".

В цитате небольшая неточность, первоначально она выглядела так: "объектные переменные в принципе не могут иметь семантику значения". Поскольку есть еще и структуры, которые как раз именно семантику значения и имеют.

Эта вся путаница из-за медвежьей услуги с неявным разыменованием ссылок. В кондовом С++ вряд ли кто-нибудь сказал бы, что если A наследует от B, то A - это B, хотя бы по той причине, что нет неявного преобразования A к B. А вот тип указателя на B запросто можно неявно привести к типу указателя на A. Но указатель и то, на что он указывает, там совершенно разные сущности. А в Шарпе смешались в кучу кони, люди...
Записан

Всего лишь неделя кодирования с последующей неделей отладки могут сэкономить целый час, потраченный на планирование программы. - Дж. Коплин.

Ходить по воде и разрабатывать программное обеспечение по спецификациям очень просто, когда и то, и другое заморожено. - Edward V. Berard

Любые проблемы в информатике решаются добавлением еще одного уровня косвенности – кроме, разумеется, проблемы переизбытка уровней косвенности. — Дэвид Уилер.
x128
Интересующийся

ru
Offline Offline

« Ответ #29 : 23-07-2010 09:25 » 

По моему всё просто, такая запись:"А а" означает что мы объявляем ссылку на объект типа А или всех его подклассов, а если А - интерфейс то
то объект всех классов которые его реализует. Вот и всё.
Записан
Страниц: [1] 2  Все   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines