Хроника пикирующих граблей. C# и другие
Sep. 3rd, 2005 12:25 am(UPDATE log: с аргументом делегата - меня научили военным хитростям)
Нормальные люди, как известно, начинать считают с единицы, программисты - с нуля. BSD'шники переплюнули программистов: у них счёт начинается с минус единицы.
Первый пример: чтобы во FreeBSD в /etc/rc.conf задать адрес на сетевом интерфейсе, надо определить переменную ifconfig_ZZZ, где ZZZ - имя интерфейса. Если одного адреса не хватает... заводится ifconfig_ZZZ_alias0. Нормальный человек назвал бы её примерно как ifconfig_ZZZ_address2, программист - помня, что основной адрес вроде как нулевой - ifconfig_ZZZ_alias1. Видимо, радикализма программистов оказалось недостаточно.
Второй пример: логфайлы. Если /var/log/messages "проротейчен" (сдвинут в сторону и на его месте открыт новый) - он становится... да, уже многие догадались (если не знали уже) - /var/log/messages.0. (Ну или /var/log/messages.0.gz, если он ещё и сжимается.) Почему 0, почему не 1? Видно, великая тайна есть в развитии BSD систем, которую не понять даже такому привыкшему уже к FreeBSD старому извращенцу, как я.
К чему это я вспомнил? Ну да, читая книгу по C# обнаружил, что старый добрый printf авторов оного не устроил (ну ещё бы, великий принцип NIH - "not invented here" - руководит чуть ли не всей деятельностью конструкторской и программной разработки). Вместо старых добрых %<n>.<m><f> (например, %d - целое число со знаком, или %12.3f - вещественное число в формате фиксированной точки с 12 позициями всего и 3 после точки) - введено нечто принципиально новое: {номер_аргумента,ширина:формат}. Сразу вспомнился gettext: его выгибоны для перестановки параметров получили естественное подкрепление, теперь локализовать выводимые сообщения в System.String.Format() значительно легче. Но первый аргумент после форматной строки... имеет номер 0! Видимо, Microsoft вместе с сетевым стеком FreeBSD подхватило вирус "минус единицы", пронумеровав ею саму форматную строку.
То что совместимость с семейством printf потеряли - об этом можно и не вспоминать. Вперёд, заре навстречу, товарищи в борьбе... Да, аналогов подходов funopen() и fopencookie() в System.IO нету, не по чину такое реализовывать. Хорошо, что хоть не мешают это делать построением своих классов, в отличие от проприетарных stdio.
С тредами мы наблюдаем другой вид чудес. Для запуска нового треда в C# надо, кроме прочих шаманских действий, занести свой метод в делегат следующего вида:
Сравнивая с другими реализациями (например, pthreads) видим отсутствие аргумента на входе:
"Гады! Редиски! Куда аргумент дели?" Не надо думать, что этот плач бессмысленный; если бы передавался параметр типа object, можно было бы через него передать любую информацию, точно так же как через void* в plain C. А что остаётся - глобальные переменные? А если у нас два или больше, например, идентичных по схеме построения worker pool'а, но с разными множествами рабочих нитей (абсолютно нормальный дизайн, однако)? Сериализовать старт новых нитей? А ведь иного и не остаётся. Тут уже подсказали, что есть типа секретный метод (Шилдт про него молчал как партизан, надо было тщательно проштудировать примеры кода чтобы понять трюк), так что исправляюсь - не всё так безнадёжно. Но - 1) почему это не описывают явно и открыто? 2) почему так через одно место? На дворе всё-таки даже для первой версии был 2000-й год, а не 1970-й.
Это напомнило мне метод старта новых процессоров в Intel SMP (подробности на developer.intel.com в мануалах к Pentium, для более поздних процессоров эта документация не дублировалось). Передать что-то процессору в том IPI (inter-process interrupt), который его запускает, например, адрес точки старта и значение хотя бы одного регистра? Да боже упаси, от этого корона свалится! Поэтому:
- старт процессоров, ясен пень, сериализуется - пока один не отрапортовал что тангаж, крен и рысканье в норме, следующие на взлёт не отправляются.
- пишется системная процедурка, которая вылавливает из памяти из глобальной переменной, кого же стартуем, и начинает соответствующую отработку.
- в область данных BIOS пишется специальное указание "после проверки процессора переходить на заданный адрес" (через него в 80286, в частности, делался переход в real mode). Что будет, если в этот момент придёт reset - можно только догадываться, скорее всего, машину придётся дёргать только по питанию.
- наконец, толкаем IPI. Процессор выйдя из спячки уходит выполнять код по стандартным FFFF:0000 в real mode. Прочитав заветный приказ по 0x0470-0x0473, уходит на процедуру ОС. Стартовав, та снимает все эти навороты чтобы reset сработал:)
Стра-ашно? Вот-вот, теперь берём передачу в делегат вместе с методом объекта - объект и уходим от этого ужаса. Довольные тем, что перехитрили всех:)
Впрочем, first-class functions с возможностью конструирования их на ходу от этого всё равно не появятся. Какое-то подобие замыканий (и опять через двуединую сущность с вертикальной улыбкой!) обещают во второй версии, да когда же она будет достаточно распространена...
Впрочем, мне это всё пока что теория - ни одной живой строчки на нём я не написал и, видимо, не скоро напишу. Может, у реальных писателей на оном, стаж которых к данному моменту достиг уже 10 лет непрерывного применения в сложных условиях борьбы с тараканами оконного интерфейса и оптимизациями языка 1С, мои плачи вызовут только ироническую ухмылку - не тем, мол, занимаешься. Ладно, посмотрим.
А вообще, впечатление от оной продукции, мягко говоря, грустное. Что стоило, например, убрать вечную проблему "к какому if относится данный else?" и сделать нормальный else-if? Неужели сохранение совместимости с дизайном 35-летней давности (старше конструкции моего пердящего драндулета) стоит того, чтобы распространять грабельное поле ещё на ближайшие 10-20 лет?
Нормальные люди, как известно, начинать считают с единицы, программисты - с нуля. BSD'шники переплюнули программистов: у них счёт начинается с минус единицы.
Первый пример: чтобы во FreeBSD в /etc/rc.conf задать адрес на сетевом интерфейсе, надо определить переменную ifconfig_ZZZ, где ZZZ - имя интерфейса. Если одного адреса не хватает... заводится ifconfig_ZZZ_alias0. Нормальный человек назвал бы её примерно как ifconfig_ZZZ_address2, программист - помня, что основной адрес вроде как нулевой - ifconfig_ZZZ_alias1. Видимо, радикализма программистов оказалось недостаточно.
Второй пример: логфайлы. Если /var/log/messages "проротейчен" (сдвинут в сторону и на его месте открыт новый) - он становится... да, уже многие догадались (если не знали уже) - /var/log/messages.0. (Ну или /var/log/messages.0.gz, если он ещё и сжимается.) Почему 0, почему не 1? Видно, великая тайна есть в развитии BSD систем, которую не понять даже такому привыкшему уже к FreeBSD старому извращенцу, как я.
К чему это я вспомнил? Ну да, читая книгу по C# обнаружил, что старый добрый printf авторов оного не устроил (ну ещё бы, великий принцип NIH - "not invented here" - руководит чуть ли не всей деятельностью конструкторской и программной разработки). Вместо старых добрых %<n>.<m><f> (например, %d - целое число со знаком, или %12.3f - вещественное число в формате фиксированной точки с 12 позициями всего и 3 после точки) - введено нечто принципиально новое: {номер_аргумента,ширина:формат}. Сразу вспомнился gettext: его выгибоны для перестановки параметров получили естественное подкрепление, теперь локализовать выводимые сообщения в System.String.Format() значительно легче. Но первый аргумент после форматной строки... имеет номер 0! Видимо, Microsoft вместе с сетевым стеком FreeBSD подхватило вирус "минус единицы", пронумеровав ею саму форматную строку.
То что совместимость с семейством printf потеряли - об этом можно и не вспоминать. Вперёд, заре навстречу, товарищи в борьбе... Да, аналогов подходов funopen() и fopencookie() в System.IO нету, не по чину такое реализовывать. Хорошо, что хоть не мешают это делать построением своих классов, в отличие от проприетарных stdio.
С тредами мы наблюдаем другой вид чудес. Для запуска нового треда в C# надо, кроме прочих шаманских действий, занести свой метод в делегат следующего вида:
public delegate void ThreadStart();Сравнивая с другими реализациями (например, pthreads) видим отсутствие аргумента на входе:
typedef void *(*pthread_startroutine_t)(void *);"Гады! Редиски! Куда аргумент дели?" Не надо думать, что этот плач бессмысленный; если бы передавался параметр типа object, можно было бы через него передать любую информацию, точно так же как через void* в plain C. А что остаётся - глобальные переменные? А если у нас два или больше, например, идентичных по схеме построения worker pool'а, но с разными множествами рабочих нитей (абсолютно нормальный дизайн, однако)? Сериализовать старт новых нитей? А ведь иного и не остаётся. Тут уже подсказали, что есть типа секретный метод (Шилдт про него молчал как партизан, надо было тщательно проштудировать примеры кода чтобы понять трюк), так что исправляюсь - не всё так безнадёжно. Но - 1) почему это не описывают явно и открыто? 2) почему так через одно место? На дворе всё-таки даже для первой версии был 2000-й год, а не 1970-й.
Это напомнило мне метод старта новых процессоров в Intel SMP (подробности на developer.intel.com в мануалах к Pentium, для более поздних процессоров эта документация не дублировалось). Передать что-то процессору в том IPI (inter-process interrupt), который его запускает, например, адрес точки старта и значение хотя бы одного регистра? Да боже упаси, от этого корона свалится! Поэтому:
- старт процессоров, ясен пень, сериализуется - пока один не отрапортовал что тангаж, крен и рысканье в норме, следующие на взлёт не отправляются.
- пишется системная процедурка, которая вылавливает из памяти из глобальной переменной, кого же стартуем, и начинает соответствующую отработку.
- в область данных BIOS пишется специальное указание "после проверки процессора переходить на заданный адрес" (через него в 80286, в частности, делался переход в real mode). Что будет, если в этот момент придёт reset - можно только догадываться, скорее всего, машину придётся дёргать только по питанию.
- наконец, толкаем IPI. Процессор выйдя из спячки уходит выполнять код по стандартным FFFF:0000 в real mode. Прочитав заветный приказ по 0x0470-0x0473, уходит на процедуру ОС. Стартовав, та снимает все эти навороты чтобы reset сработал:)
Стра-ашно? Вот-вот, теперь берём передачу в делегат вместе с методом объекта - объект и уходим от этого ужаса. Довольные тем, что перехитрили всех:)
Впрочем, first-class functions с возможностью конструирования их на ходу от этого всё равно не появятся. Какое-то подобие замыканий (и опять через двуединую сущность с вертикальной улыбкой!) обещают во второй версии, да когда же она будет достаточно распространена...
Впрочем, мне это всё пока что теория - ни одной живой строчки на нём я не написал и, видимо, не скоро напишу. Может, у реальных писателей на оном, стаж которых к данному моменту достиг уже 10 лет непрерывного применения в сложных условиях борьбы с тараканами оконного интерфейса и оптимизациями языка 1С, мои плачи вызовут только ироническую ухмылку - не тем, мол, занимаешься. Ладно, посмотрим.
А вообще, впечатление от оной продукции, мягко говоря, грустное. Что стоило, например, убрать вечную проблему "к какому if относится данный else?" и сделать нормальный else-if? Неужели сохранение совместимости с дизайном 35-летней давности (старше конструкции моего пердящего драндулета) стоит того, чтобы распространять грабельное поле ещё на ближайшие 10-20 лет?
no subject
Date: 2005-09-03 12:57 am (UTC)>Сравнивая с другими реализациями (например, pthreads) видим отсутствие аргумента на входе:
Марш читать, что такое делегаты.
no subject
Date: 2005-09-03 06:58 am (UTC)И объясните мне, клиническому тормозу, чем в этом вопросе помогут делегаты. Глава про делегаты и события в Шилдте прочитана K+1 раз, примеры разобраны, методов передачи данных, не предусмотренных определением делегата, не найдено (помимо полумифических пока замыканий). Есть какие-то секретные возможности? Тогда покажите примерчик, пожалуйста.
P.S. В ru.unix.prog - это Вы?
no subject
Date: 2005-09-03 07:27 am (UTC)Секретная возможность очень простая - экземпляр делегата может хранить ссылку на объект + функцию-член класса. Работало в ещё в .NET 1.0 (который я последний видел под Windows), работает и под современным Mono:
using System; class StatefulHandler { public StatefulHandler(int value) { this.value = value; } public void handler() { Console.WriteLine(value); } private int value; } class App { delegate void D(); static void Main() { StatefulHandler h1 = new StatefulHandler(1); StatefulHandler h2 = new StatefulHandler(2); D d1 = new D(h1.handler); D d2 = new D(h2.handler); d1(); d2(); } }>P.S. В ru.unix.prog - это Вы?
Да. Можно было определить по крайней мере сличением JID из профиля и подписи :)
no subject
Date: 2005-09-03 08:00 am (UTC)Придётся подправить сообщение.
no subject
Date: 2005-09-03 08:04 am (UTC)Я ж говорю - я тормоз:) Сверить догадался только позже, как и сейчас комментировать вдогонку;)
no subject
Date: 2005-09-03 07:14 pm (UTC)class App { delegate void D(); ...Очень дико извиняюсь за свою лень (книжку не хочется штудировать, и на с#-пример гляжу осмысленно впервый раз). Всего один маленький вопрос.
Для ссылки на сам экземпляр чужого класса/метакласса, или его метод/аттрибут - в этом языке используется специальный тип "делегат"?
То есть, чтобы сослаться на чужой объект, выделили специальный тип?!
Only just for my info. Никакого стеба.
no subject
Date: 2005-09-03 08:01 pm (UTC)К более реальному миру: на самом деле так получилось, что это массив ссылок на функции с идетичными прототипами, которые при вызове делегата вызываются по очереди. При разработке C# оставили только System.MulticastDelegate :(
Ну а так как функции, которая не метод класса, в C# не существует - для ссылки на метод класса, равно как и на метод объекта, используется уже в основном разъяснённый выше делегат.
P.S. Чтобы не брать книжку:
http://www.jaggersoft.com/csharp_course/
no subject
Date: 2005-09-03 10:04 pm (UTC)no subject
Date: 2005-09-03 08:04 pm (UTC)no subject
Date: 2005-09-03 09:55 pm (UTC)no subject
Date: 2005-09-04 06:43 am (UTC)статейку про замыкания. Английский у меня, правда, не очень.