Enterra
Компания
35,28
рейтинг
18 ноября 2014 в 08:21

Разработка → Поговорим про отличия Mono от MS.NET



С каждым днём кроссплатформенная разработка под .NET становится всё более реальной. А после недавнего анонса официальной поддержки Linux/MacOS счастливое будущее стало ещё немножечко ближе. Вышеприведённая картинка утратила свою былую актуальность, ведь исходники теперь будут под MIT. Впрочем, писать кроссплатформенные .NET-приложения можно достаточно давно — в этом нам помогает Mono. Но вот отношение к нему в сообществе довольно неоднозначное. Мне зачастую приходится слышать изречения вроде «Mono тупит, под него всё в три раза медленнее работает» или «Под Mono вообще нормально ничего не запускается». Причём очень редко доводится слышать от этих людей конкретные факты. Вопросы «А что конкретно тупит?» или «А что конкретно не работает?» повергают их в ступор. Не всех (некоторые способны на конструктивную дискуссию), но большинство. Чаще всего начинаются возмущённые ответы в духе «Да вообще ничего не работает! А если и работает, то очень медленно!». В конце беседы создаётся впечатление, что каждая конечная машинная команда под Mono работает в несколько раз медленнее, а в половине исходников стоят throw new Exception().

В этом посте мне хотелось бы немножко поделиться опытом. Не так давно мы портировали наш продукт PassportVision (анонс на Хабре) под Linux. Могу заявить, что работает он вполне нормально. Да, чутка медленнее, чем под Windows на классическом .NET от Microsoft (далее — MS.NET). Но работает вполне стабильно, а падение производительности не принципиальное. При этом продукт у нас достаточно большой и вполне попадает под категорию enterprise, а возможности C#/.NET мы используем на полную катушку. Так что завести большое серверное приложение под .NET реально — было бы желание. Также мне довелось беседовать с разными разработчиками, которые пишут что-то под Mono — истории в большинстве своём успешные.

Но почему же тогда встречается столько негатива в сторону Mono? Я считаю, что проблема в том, что люди не особо хотят разбираться в разнице между рантаймами. Запустили разок какое-нибудь .NET-приложение под Linux на Mono 2.4, а оно с ходу не запустилось — всё, Mono целиком плохой, не будем его использовать. А в итоге виноват оказывается один-единственный метод, у которого реализация немного отличается от MS.NET. Новые версии Mono выходят раз в пару месяцев, реализацию уже давно поправили, но люди всё равно продолжают ходить и хаять бедный Mono, не желая разбираться в деталях.

Сегодня я приведу несколько примеров того, чем вообще могут отличаться разные рантаймы. Конечно же, все отличия привести не получится — про это целую книгу написать можно. Да и суперкрутые проблемы из production-кода тоже привести не выйдет, зачастую слишком уж сложно их отделить от контекста программной архитектуры и привести в виде маленького понятного куска кода. Моя сегодняшняя задача — просто донести мысль того, что может отличаться в Mono и MS.NET. Надеюсь, это поможет начинающим Mono-программистам шире взглянуть на возникающие проблемы и начать с ними разбираться, вместо того чтобы развернуться и уйти со словами «Mono тупой».

Пример 1. Разные версии компилятора


Начнём с очень простого примера. Допустим, у нас есть некоторая C#-программа, а мы её хотим скомпилировать. Как мы знаем, стандартный компилятор просто оттранслирует наш C#-код в соответствующий ему IL-код и оформит в виде сборки. Причём процесс трансляции не включает в себя какие-то особо хитроумные оптимизации, ведь за них отвечает JIT. Трансляция достаточно проста, а опытный .NET-разработчик зачастую может примерно прикинуть, какие же IL-команды получатся в итоге. Но отчего-то многие разработчики уверены, что этот процесс однозначен. Я имел несколько достаточно горячих дискуссий, в которых мне пытались доказать, что «существует ровно один способ отобразить исходную программу в IL». Обычно люди мотивируют это замечательным аргументом «Есть же спецификации!». Хочу вас заверить, что в спецификациях написано много чего полезного, но ничего про единственный вариант трансляции произвольной программы там не сказано. Причём в разных версиях компилятора логика трансляции может отличаться. Я подобрал очень простой пример, который это иллюстрирует. Рассмотрим следующий код:

var numbers = new int[] { 1, 2, 3 };

В Mono 2.10+ и MS.NET мы увидим примерно следующее:

IL_0000:  ldc.i4.3 
IL_0001:  newarr [mscorlib]System.Int32
IL_0006:  dup 
IL_0007:  ldtoken field valuetype '<PrivateImplementationDetails>{de495e46-bf42-4605-a020-39ddddfe413c}'/'$ArrayType=12' '<PrivateImplementationDetails>{de495e46-bf42-4605-a020-39ddddfe413c}'::'$field-0'
IL_000c:  call void class [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [mscorlib]System.Array, valuetype [mscorlib]System.RuntimeFieldHandle)
IL_0011:  stloc.0 
// ...
.field  assembly static  valuetype '<PrivateImplementationDetails>{de495e46-bf42-4605-a020-39ddddfe413c}'/'$ArrayType=12' '$field-0' at D_00004000
// ...
.data D_00004000 = bytearray (
   01 00 00 00 02 00 00 00 03 00 00 00) // size: 12

Как видно из примера, значения исходных элементов хранятся в специальном массиве байт. А вот во времена Mono 2.4.4 (можете не верить, но по сей день есть люди, которые им пользуются) транслятор был не настолько умён:

IL_0000:  ldc.i4.3 
IL_0001:  newarr [mscorlib]System.Int32
IL_0006:  dup 
IL_0007:  ldc.i4.0 
IL_0008:  ldc.i4.1 
IL_0009:  stelem.i4 
IL_000a:  dup 
IL_000b:  ldc.i4.1 
IL_000c:  ldc.i4.2 
IL_000d:  stelem.i4 
IL_000e:  dup 
IL_000f:  ldc.i4.2 
IL_0010:  ldc.i4.3 
IL_0011:  stelem.i4 
IL_0012:  stloc.0 

На самом деле тут происходит следующее:

var numbers = new int[3];
numbers[0] = 1;
numbers[1] = 2;
numbers[2] = 3;

Функционально ничего не поменялось, но теперь у нас имеется осознание того, что разные версии компилятора могут выдавать различный код. Но этот пример не настолько интересен, т. к. поведение кода осталось прежним (если не считать небольшого проседания производительности для старых версий Mono). Давайте перейдём к более интересным примерам.

Пример 2. Отличия в работе с IL


Практика показывает, что простой «тривиальный» код работает примерно одинаково как под MS.NET, так и под Mono. А проблемы чаще всего начинаются, когда в коде появляются не такие уж и тривиальные вещи. Не так давно Джон Скит написал замечательный пост «When is a string not a string?» (русский перевод от impwx). Если кратко, то содержание поста сводится к рассмотрению следующего примера:

[Description(Value)]
class Test
{
    const string Value = "X\ud800Y";
}

Строка в C# представляет собой последовательность слов в UTF-16. А значение "X\ud800Y" не особо хорошее, т.к. включает в себя старшее слово суррогатной пары 0xD800, после которого должно бы идти младшее слово (интервал 0xDC00..0xDFFF), но вместо него идёт Y (0x0059). Проблемы начинаются из-за того, что в IL-коде для хранения аргументов конструктора атрибута используется UTF-8. Впрочем, у Джона Скита всё очень хорошо расписано, всем советую прочитать оригинальный пост.

Меня заинтересовало, как же будут себя вести MS.NET и Mono в этой непростой ситуации (подробная заметка). А вести они себя будут по-разному. Первое различие можно увидеть во время компиляции. MS.NET положит значение строки в метаданные в виде 58 ED A0 80 59, а Mono — в виде 58 59 BF BD 00 (оба значения являются невалидными UTF-8 строчками). Второе различие можно пронаблюдать запустив полученные приложения. MS.NET сможет запустить обе версии и успешно достанет значение аргумента атрибута (в виде 0058 fffd fffd 0059 и 0058 0059 fffd fffd 0000 соответственно), а Mono поперхнётся настолько невалидной строкой и вернёт null в каждом из случаев. Из-за этого маленький пример Джона Скита сразу упал, когда я попытался запустить его под Mono.

Проблемы заключаются в различной реализации конвертации строк между кодировками. Мне этот пример нравится тем, что при своей минималистичности он показывает, что MS.NET и Mono могут как формировать различный IL-код при компиляции, так и по-разному с ним работать при запуске приложения.

Пример 3. Баги в Mono


Да, в Mono есть баги. И да, их не очень мало, приходится с этим жить. К счастью, попадать на них приходится нечасто, а из открытых багов не так много критичных. Расскажу вам одну историю: при портировании PassportVision на Mono я наткнулся на один не очень приятный момент. Мне пришлось заниматься кодогенерацией и часть логики создавать на лету в зависимости от ряда условий. Я создавал новые типы через TypeBuilder, а типы эти реализовывали определённые интерфейсы. Я узнал очень много нового про генерацию кода в Mono, но большую часть очень сложно кратко поведать в отрыве от контекста. А вот один баг воспроизводился очень легко: метод TypeBuilder.CreateType() не проверял область видимости объявленных интерфейсов. Т. е. код

private interface IFoo {}
// ...
void Main()
{
  var assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(
      new AssemblyName("FooAssembly"), AssemblyBuilderAccess.Run);
  var moduleBuilder =
  assemblyBuilder.DefineDynamicModule("FooAssembly");
  TypeBuilder typeBuilder = moduleBuilder.DefineType("Foo",
  TypeAttributes.Public,
      typeof(object), new[] { typeof(IFoo) });
  typeBuilder.CreateType();
}

падал под MS.NET (как ему и положено), но зато прекрасно отрабатывал под Mono. Меня такое положение вещей не очень устраивало, поэтому я пошёл и завёл баг. Надо отдать ребятам из Xamarin должное — поправили они его через 5 дней. Если вы пользуетесь Mono 3.8.0 (которая вышла в августе), то у вас этот баг ещё есть, а вот Mono 3.10.0 вышел с исправлением.

Данная проблема не особо критична, но ряд неудобств она мне всё-таки доставила. А ведь в баг-трекере всё ещё висит около 5000 открытых проблем. Поэтому нужно держать в уме, что определённые ошибки в Mono могут быть и что от версии к версии поведение некоторых мелких моментов может меняться (возможно, из-за моего баг-репорта у кого-то перестало что-то работать после обновления). Если есть возможность, то лучше сидеть под последней стабильной версией Mono.

Пример 4. Баги в .NET


После таких разговоров обычно начинают лететь камни в сторону Mono: мол, какой-же он плохой, баги в нём есть. И все как-то забывают, что в Microsoft баги тоже делают. Следующая история стоила определённого количества нервных клеток одному моему товарищу. После очередного коммита билд-сервер сообщил, что тесты упали. Причём на рабочей машине они на отличненько проходили, а вот на билд-сервере падали. Я опущу увлекательное описание того, как происходил поиск баги, и перейду к сути: на билд-сервере и рабочей машине стояли разные версии .NET Framework: 4.0 и 4.5. А сама бага заключалась в том, что строчка

new Uri("http://localhost/%2F1").AbsoluteUri

выдаёт разный результат в зависимости от TargetFramework (подробная заметка). В 4.0 была бага с экранированием слеша (он же %2F): данная строчка возвращала http://localhost//1. В 4.5 багу поправили в соответствии с RFC 3986, новый результат — http://localhost/%2F1.

А что же в Mono? А там была аналогичная бага, которую поправили в августе (исправление включено в Mono 3.10.0). Этот пример учит нас тому, что одинаковые проблемы могут возникать в различных реализациях .NET и исправляться с новыми версиями. Действительно кроссплатформенное приложение должно либо не использовать менявшуюся от версии к версии функциональность платформы, либо использовать её очень грамотно, чтобы оставаться работоспособным под различными рантаймами.

Пример 5. Тонкости реализации стандартных классов


Приведу ещё одну поучительную историю. Ребята из одной хорошей фирмы как-то раз решили использовать структуры в качестве ключей для хеш-таблиц. Ничто не предвещало беды, приложение работало нормально. Немного тормозило, но не так, чтобы прям слишком критично. А потом в один прекрасный день ребята решили запустить своё приложение под Mono. И о чудо: оно начало работать в разы быстрее. «Ох, какой хороший Mono, как же быстро под ним всё работает», — обрадовались ребята. Единственное, не давала покоя мысль, что не должно так быть, что где-то что-то не так.

Чтобы понять данный пример, нужно немножко почитать про реализацию GetHashCode у структур в MS.NET (хороший хабрапост). Если кратко, то есть две версии для хеш-функции: одна для структур без ссылочных полей и свободного пространства между полями, а другая — для всех остальных. Как мог догадаться вдумчивый читатель, со структурой-ключом у наших ребят было не всё хорошо (а именно, были «дырки» между полями; прописать явный GetHashCode() никто не удосужился). А в этом случае используется вторая версия хеш-функции, которая базируется на основе первого поля структуры. Рассмотрим на примере:

var a1 = new KeyValuePair<int, int>(1, 2);
var a2 = new KeyValuePair<int, int>(1, 3);
Console.WriteLine(a1.GetHashCode() == a2.GetHashCode());
var b1 = new KeyValuePair<int, string>(1, "x");
var b2 = new KeyValuePair<int, string>(1, "y");
Console.WriteLine(b1.GetHashCode() == b2.GetHashCode());

Данный код выведет False True под MS.NET. Хеши a1 и a2 буду различаться (они будут считаться на основе всех полей), а хеши для b1 и b2 совпадут (они будут считаться только на основе первого поля). Разработчики Mono решили не заморачиваться с кучей разных версий хеш-функции: они написали одну, которая работает на основе всех полей (см. GetHashCode и InternalGetHashCode). Соответственно, приведённый код выведет False False под Mono, ведь все хеши будут различными.

Вернёмся к нашим весёлым ребятам с хеш-таблицей. Если посмотреть на ситуацию под правильным углом, то их приложение не летало под Mono, а тормозило под MS.NET. А тормозило оно из-за того, что очень много ключей имело одинаковый хеш из-за совпадающего первого поля. Казалось бы, мелочь, но на производительность она оказала достаточно сильное влияние.

Знаете, когда я кому-то начинаю рассказывать про внутренности .NET, то меня часто упрекают фразами «Да зачем всё это знать? Подобные детали внутренних реализаций никогда в реальной жизни не пригодятся». А я отвечаю: «Ну-ну, разумеется, не пригодятся никому и никогда».

.NEXT


Подобные истории я могу травить очень долго, но в этом посте мне хотелось лишь обозначить общую проблематику и заставить людей ещё раз подумать. Подумать о том, что живём мы в сложном мире, в котором ваша C#-программа может работать по-разному в зависимости от окружения. Разница в поведении упирается во вполне конкретные вещи, с которыми можно разобраться, и добиться того, чтобы .NET-приложение работало быстро и стабильно под разные платформы.

Если вы хотите послушать больше весёлых историй, то приходите на мой доклад в рамках конференции .NEXT (8 декабря 2014, Москва, Radisson Славянская). Я продолжу обсуждать тему разных платформ, расскажу про особенности Mono по работе с памятью и отличия в реализациях JIT-компиляторов. А ещё я весь день буду бродить по площадке, так что меня можно будет поймать и пообщаться на тему .NET.

Также надеюсь, что у Хабражителей хватает своих весёлых историй. Буду рад их послушать =)
Автор: @DreamWalker
Enterra
рейтинг 35,28
Компания прекратила активность на сайте

Комментарии (77)

  • +3
    Скажу что мне доставило некоторый дискомфорт при использовании Mono — это почти всё, что связано с криптографией. Можно регулярно славливать Exception'ы на коде, который много лет уже успешно работает в MS.Net. «Хит» здесь был
    invalid block length at Mono.Security.Cryptography.SymmetricTransform.FinalEncrypt

    Выпадал настолько часто, что я его даже запомнил.
    • 0
      Я очень надеюсь, что Xamarin в скором будущем просто скопипастит всю криптографию из MS.NET. Благо, новая лицензия позволяет.
      • 0
        Мы отчасти тоже решали эту проблему копипастой из исходников MS.NET.
        Потому что, фактически, эта ошибка происходила в «бутылочном» горлышке, через которое проходят половина методов криптографии, в частности, CryptoStream. И природа ошибки в разных сценариях тоже была разной. Чтобы обходить это, что-то прикрывали «костылями», где-то копипастили исходный код из MS.NET, что-то через некоторое время фиксили разработчики Mono. В итоге всё заработало, но «осадочек остался».
        И не думаю, что удастся просто всё взять и всё скопипастить — в криптографии MS.NET много что завязано через WinAPI на нативную «crypt32.dll».
        • 0
          Ну, чем больше получится перетащить из MS.NET в Mono, тем я буду больше радоваться =) Понятно, что на 100% реализация таких больших кусков вряд ли будет совпадать, но жить явно станет проще.
        • 0
          У вас был какой то эксклюзивный доступ к исходникам ms.net? ведь открыли их буквально только что.
          • +4
            Под лицензией «только смотреть, но не трогать» они были доступны уже много лет. Ну и декомпиляторы никто не отменял.
          • 0
            Просто ILSpy.
    • 0
      Из криптографии там самое весёлое — SslStream, который совместим со всем чем угодно, но только не с самим собой. Причём патч существует уже года три как минимум, но в апстрим его так и не приняли.
    • 0
      С криптографией у самих MS всё сложно. Основополагающий интерфейс System.Security.Cryptography.ICryptoTransform нельзя использовать даже в родном для MS виде Portable Class Library, не говоря уже про другие платформы. Я так понимаю, что в криптографии у них планируется большой рефакторинг, поэтому временно портирование никуда не идёт.
  • НЛО прилетело и опубликовало эту надпись здесь
    • +2
      Когда я говорил людям про Mono 5 лет назад, то на меня очень странно косились. Знаете, наверное они были правы. Как раз вышла версия 2.4 (LTS, кстати говоря, ей до сих пор пользуются). Сейчас я уже плохо понимаю, как под неё что-то можно было писать. А вот текущая версия Mono (3.10) более или менее пригодна для продакшена. Надеюсь, что в ближайшие год-два мы получим Mono, под который можно нормально писать и совсем не страдать.

      Запись докладов обязательно будет. Правда, сразу после конференции к ней смогут получить доступ только участники. В открытый доступ запись попадёт спустя какое-то время.
      • НЛО прилетело и опубликовало эту надпись здесь
        • +1
          MS сделает некоторые шаги в сторону открытия исходников .NET Framework
          Не некоторые шаги, а .NET 4.6 под MIT-лицензией.
        • 0
          Ну, ядро скоро обещают полностью открыть, да. Но тот же WPF открывать вроде бы не собираются, а на Linux/MacOS его хрен портируешь. Да и вообще, в MS.NET очень много вкусных штук, которые на WinAPI завязаны, их не так просто перетащить в Mono. Так что классический .NET-рантайм никуда не исчезнет, будет существовать параллельно с Mono.
          • 0
            а на Linux/MacOS его хрен портируешь
            Silverlight вроде является подмножеством WPF, а его порт в природе существует, Moonlight зовётся.
            • +2
              Мне довелось долгое время принимать участие в разработке достаточно большого Silverlight-приложения: Grapholite. SL не взлетел, пришлось портировать под Windows Store и Google Play, но впечатлений осталось очень много. Вот что я могу вам сказать:
              1. Сколько бы раз MS не говорили слово «подмножество», никаким подмножеством там и близко не пахнет. Многие вещи сделаны слишком иначе. После краха SL у нас была мысль портировать с SL на WPF, но это очень большая работа.
              2. Даже если бы SL и был подмножеством WPF, то он был бы очень грустным подмножеством. Из WPF реально очень много выкинули, чтобы сделать SL. Из-за этого для реализации хоть сколько-нибудь нетривиальных вещей приходится очень сильно извращаться и забивать со всех стороны костыли.
              3. С Moonlight-ом было бы всё хорошо, если бы он работал нормально. Да, базовые примеры в нём запускаются, но как только выходишь из песочницы и начинаешь делать что-то нормально, то Moonlight отказывается работать.

              Портировать WPF на Mono в нормальной комплектации (чтобы было можно комфортно работать) в теории возможно, но на практике это настолько большая и сложная задача, что вряд ли кто ей будет заниматься.
              • 0
                Помнится, году в 2012-м мы пытались портировать Silverlight-приложение на Moonlight, закопались в проблемах и багах. В итоге нашли такой печальный твит:
                twitter.com/migueldeicaza/status/263816476551700481
                и официально отказались от поддержки мунлайта.
                С тех пор что-то изменилось?
          • НЛО прилетело и опубликовало эту надпись здесь
            • +5
              С учетом того, что WPF особо, еще раз повторю: ОСОБО не взлетела

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

              что все же есть аналоги WPF\XAML

              Аналогов хватает, но ни одного такого, которым я бы согласился пользоваться в продакшене для разработки сложного UI.

              Еще просьба к Вам: когда опубликуют Ваш доклад, ни могли бы скинуть мне ссылку на него?

              Лично я к видеозаписи и публикации материалов вообще никакого отношения не имею. Обратитесь лучше к организаторам конференции.
              • –3
                Аналогов хватает, но ни одного такого, которым я бы согласился пользоваться в продакшене для разработки сложного UI.

                А как же FXML от JavaFX? Или он тоже плох?
                • +2
                  здесь про .NET based разговор идёт
                • +1
                  1. Согласен с Borz, мы тут .NET обсуждаем.
                  2. Ну, быть может я не такой уж и эксперт в FXML, но на мой субъективный взгляд FXML на пару порядков до WPF не дотягивает. Да и с распространённостью у него как-то не очень. Поправьте меня, если я ошибаюсь.
                  3. Если вы приведёте мне пример действительно сложного GUI от энтерпрайз-приложения на FXML, то буду признателен.
                  • 0
                    раз уж решили раскрыть таки тему, то на JavaFX не обязательно через FXML писать — можно и напрямую классами фигачить и в этом случае, они «на равных» (не считая багов) с SWT получаются за тем отличием, что для SWT под каждую ОС своя либа подкладывается, а под JavaFX только одна (а в JRE8 так и вовсе не надо — «изкаропки» идёт).
                    до кучи получаем, хоть и пока несколько сыроватую, возможность сборки под Android/iOS за счёт портов JavaFX (Dalvik | RoboVM).

                    но багов пока ещё хватает (либо я криво «верстаю») в UI элементах. из «ближайших» — наезд содержимого центральной части BorderLayout на боковую (например west) область при достижении некоего указанного «минимума» (например указал, что center не уже 500px должен быть).
                    • 0
                      Скорее всего вы «немного» криво верстаете. Так как, к сожалению, в компонентах не всегда адекватно называются свойства и механизм приоритетов не всегда ясен.
                      Хотя баги тоже есть.
                  • 0
                    1. Как-то упустил этот момент, извините.
                    2. Если вы про сравнение WPF и JavaFX, то разумеется, WPF будет иметь больше возможностей. Как минимум потому, что он вышел куда раньше.
                    Если же про сравнение XAML и FXML, то в FXML можно:
                    • собственно, задавать структуру GUI;
                    • создавать функции на скриптовых языках;
                    • создавать объекты (правильно объявленные, Java же)
                    • использовать свои элементы компоновки
                    • Использовать Java локализацию для строк сразу же
                    • Возможность включать один FXML в другой

                    Как плюс можно отметить менее странное начало. Вместо пространства имен используется стандартные Java импорты.
                    В XAML есть еще какие-то дополнительные фичи, которые этим не покрываются? (Когда я учил XAML, там было WPF 3.0).
                    3. Я бы с радостью, но в энтерпрайз еще повсеместно не вошла даже Java 8 (ибо Java энтерпрайз). Однако, существует достаточно проектов для различных миграций. К примеру, есть Open Dolphin.
                    Если нужен пример сложного масштабируемого приложение, то можете посмотреть стандартный приложение-пример от Oracle — Ensemble8. Как по мне, весьма сложное GUI.
          • 0
            То что они сейчас открыли уже очень хорошо покрыто тестами чтобы никто не мог сломать реализацию для Windows, а WPF — очень большой по коду и думаю его намного сложнее покрыть тестами. Думаю в этом главная сложность для открытия WPF.
          • 0
            WPF как раз не так сильно завязан на WinAPI (точнее, завязан самый нижний слой — MILCore — и на Direct3D; но там только рендеринг, по сути).

            Так что не торопитесь хоронить эту идею. Все может быть.
            • 0
              Ну, в теории-то может. Я очень надеюсь на светлое будущее, в котором будет удобная GUI-система, на которой под все платформы писать можно. Но вот только наступит это светлое будущее не завтра, а разрабатывать нужно уже сегодня. У MS и Xamarin слишком много других важных запланированных задач на несколько лет вперёд, чтобы они ещё и портом WPF занимались. На 5 лет вперёд рынок прогнозировать вообще нереально, но в бизнес-стратегию подобные планы включать точно не стоит. Пока нужно разрабатывать с тем, что есть сейчас, а фоном потихоньку следить за происходящим.
      • 0
        5 лет назад — это еще не так страшно. А у нас первый Linux-релиз был на mono 1.9. У неё тогда было вообще очень много интересных глюков, даже BackgroundWorker фигово работал. Тогда переход на 2.2 казался если не раем, то чем-то в этом духе.
        • 0
          Ох, как тяжко было быть вами. Я вообще плохо понимаю как Mono 1.* можно было для чего-то большого использовать.
  • +2
    метод TypeBuilder.CreateType() не проверял область видимости объявленных интерфейсов. Т. е. код
    Mono вообще много чего не проверяет. Я как-то случайно натравил ldfld (вместо ldsfld) на статическое поле, так Mono вытащил значение instance-поля с тем же номером, в итоге в переменной типа List оказалась строка. Я тогда ещё долго смотрел на это счастье в отладчике и пытался понять, как же так.
    • +2
      Ох, полностью с вами согласен. Если IL-код хоть немного похож на что-то вменяемое, то Mono его жрёт и спокойно исполняет. Иногда даже получается ожидаемый результат. В это же время MS.NET на любой самый мелкий косяк начинает закидывать экзотическими исключениями, по которыми документация практически отсутствует.
  • +2
    Разрабатывал когда-то сервачок на .NET/Mono, крутился в продакшене как на винде, так и на лине, особого геморроя с Mono не было… Причем работал и дебажил в студии под виндой, а после уже заводил на линь, практически без изменений, и все просто работало… :) Правда, пока не подключился дополнительный "разработчик", но это уже другая история)))
    • +1
      Если бы ещё не накладные расходы, которые вылезают при использовании XSP/mod_mono, было бы вообще замечательно. Для примера см. бенчмарк: NancyFx с самописным хостом на базе libevent обходит NancyFx хостящийся на XSP в 21 раз.
      • 0
        Неужели все настолько плохо под линуксом и никак не сравнять скорость работы с windows?
        • 0
          В следующем раунде мой evhttp-sharp отработал с 91,557 запросов в секунду. Конкурировать с виндовым http.sys (он всё ж таки в ядре ОС работает) на базе деревянного HTTP-сервера из состава libevent дальше сложно, надо брать proxygen или что-то типа него.
          • 0
            Это уже приемлемо, найти бы еще статью на хабре, как это все правильно приготовить ;)
            • 0
              Для ASP.NET WebPages/MVC — никак, там только через Hyperfastcgi. Для всего остального можно воспользоваться OWIN-хостом из состава либы.
    • +2
      Ну, «простой» код действительно запускается нормально. Проблемы чаще всего начинаются в тот момент, когда вы собираетесь делать что-нибудь «эдакое».
      • 0
        У нас в GitExtensions основные проблемы есть с GUI, WinForms под Mono работает ужасно (что и следовало ожидать по названию). Может быть переписывание на Gtk# и решило бы проблемы но очень не хотелось бы потерять редактор форм, тем более я думаю в нем багов тоже хватает.
        • 0
          В MonoDevelop(Xamarin Studio) есть редактор форм для GTK#, кроме того есть отдельный Glade
          • 0
            Спасибо. А у кого-нибудь есть опыт использования Gtk# в продакшене? Насколько он стабильный и удобный в использование?
            • +1
              Я использовал. Не сказать, чтоб в больших проектах, но опыт есть. Gtk# биндится к старой версии Gtk2. И под Win и Mac выглядит это довольно паршиво. В целом со стабильностью проблем не замечал.

              Сейчас смотрю в сторону Xwt, но пока только приглядываюсь
            • +1
              Делали на Gtk# клиент для Kebrum. Стабилен как совок времён хрущёвской оттепели. По удобству примерно так же.
        • 0
          Я изначально имел ввиду серверную версию. Увы, вменяемого GUI под все платформы нет.
          • 0
            Qt только если
            • 0
              На мой взгляд, QML — это самая вменяемая разметка для GUI после XAML-семейства. Но, увы, QML под .NET пока не изобрели, а на плюсах после шарпика без особой необходимости писать не хочется.
              • –1
                О, да, как же сложен C++ даже в QT после C# :(
                • +1
                  Ну, я бы не сказал, что он прям сложен — при должном усердии вполне можно со всем разобраться. Другой вопрос, что в C# платформа об очень многих вещах думает за меня, а я могу сосредоточиться на высокоуровневых штуках. А в С++ приходится быть намного более внимательным и тратить время на более низкие уровни абстракции.
  • 0
    При переносе программы от MS.NET 2.0 на более высокую версию столкнулся с тем что поменялась реализация одной из коллекций — условно говоря, там где был массив стал итератор или как-то так — не помню уже точно, давно было. Суть в том, что в тексте стояли foreach, а это синтаксический сахар с кучей условий — типа если есть итератор то используем его итп. Так вот, если не перекомпилировать то с новым дотнетом выпадала ошибка на отсутствие метода, но перекомпиляция того же кода давала рабочий код. Думаю что при переходе на Mono или между версиями mono возможны подобные эффекты тоже.
  • +2
    В Моно 2.2 мой сервер работал, но страшно медленно. Разобрал, изучил, нашел, что Array.BinarySearch выполняется в 20 раз медленее, чем в .Net на больших массивах и задал себе вопрос: что это вообще за фигня — поиск по массиву в критичной по времени серверной части? Переделал на хеш таблицы и стало все замечательно на обеих платформах (± мелочи). С тех пор код пишу сразу с прицелом и на Mono, с Path.DirectorySeparator и с профайлингом mprof.
    • +1
      Надо будет проверить. Реализация поиска с тех времен не поменялась (Mono-2.2-BinarySearch, Mono-3.10-BinarySearch). Microsoft явно сделали лучше (MS.NET-4.5.2-BinarySearch). Для SZArray есть нативная имплементация (MS.NET-4.5.2-TrySZBinarySearch), так что понятно почему она работает быстрее. Вопрос в том — насколько?
      • 0
        Не надо проверять, надо отправить пул-реквест с переносом кода (либо референс на весь файл, либо partial-class, либо просто копипаст с переформатированием) следуя рекомендациям. Код array.cs под MIT лежит тут.
        • 0
          Портировать надо вот эту штуку:
          [MethodImplAttribute(MethodImplOptions.InternalCall)]
          private static extern bool TrySZBinarySearch(Array sourceArray, int sourceIndex, int count, Object value, out int retVal);
          

          Нативные исходники где-нибудь доступны?
  • +1
    Моно пробовал давно, когда правил балом еще .Net 1.1, поэтому все истории про MS.NET :)

    Первая про сортировку массивов, был у нас код который сортировал массивы из пары сотен тысяч элементов, предвкушая возмущения, скажу что сортировка для UI. И так все отработано, сортировка работает достаточно быстро для того, что бы не быть заметной пользователям. Но в какой-то момент времени получаем тикет, долго грузится система, начинаем искать. Находим, дело в том, что MS изменила алгоритм сортировки с целью его оптимизации в .Net 3.5 и алгоритм стал нестабильным! По не понятным причинам, он мог то работать с прежней скоростью, то проваливаться в производительности в сотни и тысячи раз! Полечили своей реализацией квиксерча, после выхода .Net 3.5.1 все было исправлено, вернулись на родную реализацию.

    Вторая про работу в много экранном режиме под виртуальными машинами, всей подноготной не помню, но у нас клиенты работали в облаке через удаленный доступ предоставленный сервисами Citrix, все бы хорошо но! У некоторых клиентов все элементы меню, всплывающих окон и подобных элементов UI были просто перечеркнуты красным крестом, такое можно увидеть при падении UI потока при отрисовке содержимого контрола. Расследование показало, что .Net в таком режиме работы не может определить основной монитор, если он не установлен в системе как левый нижний! А у .Net внутри много кода который оперирует аксиомой, что основной монитор всегда не null, результат падение всего кода внутри .Net использующего информацию о основном мониторе.

    Исправить и то и то, стало возможным только из за желания разобраться, что там не так :)
  • 0
    А что используется в вашем PassportVision, упомянутом в статье, для GUI?
    Ведь WPF под Mono не работает. WinForms?
      • 0
        Тогда мне не совсем понятно, как именно они заставили его работать под Mono :O
        • +1
          Под Mono мы портировали часть без GUI. Есть много заказчиков, которые хотят запускать PassportVision под Linux-сервером, и ни одного, кто хотел бы запускать PassportVision под Linux-клиентом.
          Сценарий у многих такой, что люди хотят делать самую важную часть работы (распознавание документа) на сервере. Далее либо используется наша верификационная форма на Windows-клиенте (проверяются полученные с сервера данные), либо используется собственный интерфейс заказчика (многие хотят именно встроить PassportVision в свою систему без появления лишних форм).
  • –1
    Основная претензия к mono приходит из-за процесса установки и запуска приложений. Миллионы каких-то «сборок», которые зачем-то компилируются (как будто это gentoo, а не debian), первый старт любого приложения под mono сопровождается таким количеством IO, что за это можно загрузить пару компьютеров с начала и до запука FF.

    Памяти приложение на mono отъедает так, как будто у компьютера этой памяти сотни мегабайт свободной. Заметим, отъедает «под собственные нужды» без особого смысла. На фоне нативных приложений линукса, у которых основной runtime — это libc, mono выглядит неповоротливым монстром.
    • 0
      Я считаю, что вы излишне драматизируете. Да, проблемы есть, но в последних версиях они не настолько критичны. С установкой и первым стартом всё действительно не так весело, а вот с расходом памяти нужно смотреть по конкретной программе. «Пустая» программа у меня кушает ~1.5MB, так что рантайм просто так сам по себе не особо память расходует. Проблемы идут из-за хреново сделанной сборки мусора и аллокации памяти под объекты. Если выставлен boehm, то вообще вешаться можно. С sgen ситуация заметно улучшилась, но там свои приколы с выделением памяти есть, с которыми всё очень сложно. Я крайне надеюсь, что после открытия MS-рантайма Мигель первым делом побежит воровать GC. А пока что остаётся только оптимизировать под Mono, увы.
      • –1
        Я не драматизирую, я описываю позицию пользователя. «Я хочу эту программу» — «поставь ЭТОТ РАНТАЙМ» — «ЗАЧЕМ?» — «Т А К Н А Д О !!111».

        Примерно как если бы в кафешке «я хочу вот это пироженное» — «ок, но вам сначало надо съесть пригоршню вот этих камешков. Они безвредны и позволяют нам обеспечить общий техпроцесс в этом кафе и вот в той забегаловке».

        С точки зрения пользователя весь .net и mono — это чистой воды блотваря, которую втюхивают вместе с полезным приложением. Одного класса с всякими тулбарами. Они нужны разработчику, но нафиг не сдались пользователю.
        • +6
          А тут встаёт вопрос о стоимости и времени разработки.
          Например, я могу написать программу на C# за год. Она будет работать не всегда быстро, жрать не так мало памяти, но уже через год пользователь сможет ей пользоваться.
          Эту же программу я могу написать на С++, для примера, за 2 года. Она будет работать быстрее, жрать меньше памяти, но будет у пользователя только через 2 года.
          А могу написать на ASM. Причём, сделать отдельную версию под каждую архитектуру и под каждую операционную систему. К примеру, сделаю я это за 15 лет. Только вот через это время программа уже никому нужна не будет.

          Никто не говорит, что вы должны все приложения писать под .NET. Напротив, вы должны стараться выбирать конкретную платформу и язык программирования исходя из задачи. .NET — это всё-таки больше про энтерпрайз. Есть определённые бизнес-требования, чаще всего вам нужно выпустить в короткие сроки качественное и стабильное приложение. А для этого разработчику нужны хорошие инструменты. Даже если страдает производительность и расход памяти, это может быть вполне нормальная цена за быстроту и стоимость разработки — всё зависит исключительно от бизнес-условий.

          Резюмирую. Если вы хотите маленькое пирожное за 50 рублей, то вполне можно найти кафе без камешков. А если вы хотите 10-ярусный торт со стриптизёршей внутри и завтра, то придётся смириться с тем, что кафе нужно выбирать специфическое, а вместо 200 грамм тортик будет весить 200кг.
  • 0
    Картинка в статье не соответствует действительности, потому что MSFT заявили что никаких преследований по патентам не будет:
    github.com/dotnet/corefx/blob/master/PATENTS.TXT
    • 0
      Ну так я в самом первом абзаце и написал:
      Вышеприведённая картинка утратила свою былую актуальность
      • 0
        «Чукча не читатель, чукча писатель.»
        Сорри не заметил =)
    • 0
      Вышеприведённая картинка утратила свою былую актуальность, ведь исходники теперь будут под MIT
      Третье предложение в тексте. опоздал :)
  • 0
    Не знаю на сколько хорошо Mono работает на десктопах но например достаточно простое приложение под Android работающее с помощью Mono (HighLoad++ например, которым недавно так сильно хвастались разработчики на последней конференции от Yandex) работает неудовлетворительно медленно.
    • 0
      Ну, опять-таки, нужно смотреть что конкретно работает медленно. «Простое» — это не описание. Проседание производительности может вызвать «простой» код на несколько строк. Быть может, много объектов создаётся, и GC начинает грустить. Может быть рендерится что-то хитрое. Скорее всего проблемы со скоростью упираются в два-три важных момента, по которым нужно проводить оптимизацию.
  • 0
    Насчёт багов в .Net и mono.
    DateTime.Now.ToString("m");
    
    В Mono возвращал «Ноябрь 1»,
    Windows phone 7 «1 ноябрь»
    Windows phone 8 «01 ноябрь»

    Писал в моно баг — исправили, теперь как в .NET…
    • 0
      Отлично, просто отлично. Добавляю себе в коллекцию.
      А у вас не осталось где-нибудь номера бага?
      • 0
        Нет, не помню. Сам репортил в bugzilla.xamarin.com — не нашел. Ещё они имеют нехорошую привычку скрывать некоторые багрепорты…
        • 0
          У них ещё поиск дурацкий — очень сложно что-то найти.
      • 0
        Нашел . Там даже интереснее, Xamarin возвращает одно, а моно другое :)
  • 0
    Есть здоровенный проект под ms.net с >1000 тестов под MSTest. Как выполнить их под Mono? Портировать на NUnit или можно все таки запустить mstest под mono?
    • 0
      Ну, быть может и можно как-то запустить, но если вы собираетесь работать под Mono, то я бы всё-равно посоветовал переписать под NUnit. Думаю, это не должно быть такой уж большой задачей.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка