Как-то раз в одном чате прозвучала идея сравнить разные технологии для написания оконных приложений. В частности, под Windows. Используя средства языка C#. То есть, конечно, можно это сделать и с помощью C++, Python, Rust, JS и других языков, но мы же шарписты, нам интереснее именно то, что мы можем сами использовать, не меняя язык программирования.
Итак, оконные приложения на C#. Их можно написать, используя:
Windows Forms
WPF
WinUI
.NET MAUI
Avalonia
Uno Platform
Такое многообразие фреймворков обусловлено тем, что язык развивался, менялись подходы, совершенствовались технологии. А в какой-то момент к разработке подключились сторонние разработчики и к средствам Microsoft добавились Avalonia и Uno.
Таким образом, для того чтобы начать писать оконные приложения надо решить, используя какую технологию использовать. Но, кроме того, неплохо бы понимать, где будет приложение использоваться – только на Windows или есть необходимость в запуске на MacOS и Linux. А может быть еще и на IOS и Android?
В общем, в этой статье попробуем разобраться в возможностях этих фреймворков и сравним их. Сравнивать будем по таким критериям:
Платформы, в которых можно запустить приложение
Сложность разработки
Потребляемые ресурсы
Скорость запуска
Для корректности сравнения будем использовать одну логику для всех приложений. Более того, попытаемся сделать их максимально похожими друг на друга.
Часть 1. Логика
Итак, чтобы не сравнивать пустые приложения без какой-либо функциональности, и при этом не перегружать излишним наполнением, реализовал простейшую версию игры Крестики-Нолики. Логика самой игры простая: есть два игрока Х и О. Начинает игрок Х потом ходит О. Кто первым соберет три своих символа в ряд — выигрывает, если после последнего хода победителя нет, то ничья. Игра осуществляется с одного устройства, по очереди.
Весь код логики практически один в один переходит из приложения в приложение, за исключением некоторых особенностей в разных фреймворках. Например, в Авалонии нет встроенного механизма отображения всплывающих окон (Message Box) и для того, чтобы он появился надо установить дополнительный пакет. Не сложно, но нюанс. В некоторых фреймворках нельзя напрямую посчитать кнопки из интерфейса и надо придумывать обходные пути.
Важно также отметить, что везде, кроме WinForms по-хорошему следует использовать паттерн MVVM, однако для простейшего приложения заморачиваться не стал и работает все напрямую.
Часть 2. Пользовательский интерфейс
Разработка UI в приложениях C# отличается не очень сильно друг от друга. Выделяется разве что WinForms, где элементы интерфейса размещаются обычно вручную в графическом редакторе, что в свою очередь весьма дружелюбно по отношению к людям без опыта.
Еще можно размещать элементы вручную в WPF, однако рекомендуется все же выстраивать интерфейс вручную задавая параметры в коде страницы.
Осложняет процесс разработки внешнего вида отсутствие интерактивного отображения получаемого результата в MAUI и WinUI – там посмотреть на результат можно только после сборки и запуска. Да, есть такая функция как Hot Reload, когда изменения будут появляться после горячей перезагрузки без перезапуска всего приложения, но тоже не без нюансов. Во-первых, при горячей перезагрузке теряется состояние приложения (если оно отдельно не сохраняется где-то), например при игре уже есть несколько крестиков и ноликов, то после Hot Reload они «забываются». Во-вторых, бывает, что изменения не отображаются и все равно приходится перезапускать приложение. Не баг, а фича, как говорится.
В целом же привыкнуть можно ко всему и даже потом возвращаться к WinForms становится тяжко – хочется все настраивать самому.
В конечном итоге интерфейс готовых приложений получился таким:
Windows Forms
WinForms WPF
![WPF WPF](https://habrastorage.org/getpro/habr/upload_files/c8c/859/ffc/c8c859ffcc69432b97b8e3f85ebba4e9.png)
WinUI
![WinUi WinUi](https://habrastorage.org/getpro/habr/upload_files/6c1/c2b/97c/6c1c2b97ca269b18b0b3d2d708ce3eeb.png)
MAUI
![MAUI MAUI](https://habrastorage.org/getpro/habr/upload_files/4b1/98d/328/4b198d32821e37d2170d4ecb822f4c55.png)
Avalonia
Avalonia Uno Platform
Uno Platform
Я не сильно старался сделать их идентичными, потому что и задачи такой не было, да и на последующие измерения цвет кнопочек не повлияет. Более того, можно обратить внимание, что некоторые между собой похожи. Это результат применения в них компонентов WinUI. В MAUI дополнительно используются фирменные шрифты и MaterialDesign – поэтому кнопки со скругленными углами. А еще WinUI подтягивает основную тему из системы и поэтому все, кроме WinForms и WPF темные. Соответственно в первых двух нужно отдельно добавлять обработку светлой/темной темы.
Часть 3. Платформы
Тут мы подходим к глобальным возможностям всех фреймворков. Итак, таблица ниже показывает, под какие платформы может быть собрано приложение.
Фреймворки | Windows | MacOS | Linux | Android | iOS |
WinForms | + | ||||
WPF | + | ||||
WinUI | + | ||||
.NET MAUI | + | + | + | + | |
Avalonia | + | + | + | + | + |
Uno Platform | + | + | + | + | + |
Как видим, что вполне логично, все шесть фреймворков позволяют писать оконные приложения для Windows. Но есть и различия. Они кроются как в кроссплатформенности MAUI, Avalonia и Uno, так и в компонентах, которые используются для отрисовки графического интерфейса.
Углубляться сильно не будем в дебри, но стоит знать, что MAUI Авалония используют компоненты WinUI, Uno в свою очередь может реализовывать оконное приложение как в WPF варианте, так и WinUI. Это может быть полезно в отдельных случаях.
Кроме того, нативные фреймворки WinForms, WPF и WinUI отличаются технически:
- Windows Forms использует GDI+ для отрисовки интерфейса
- WPF использует DirectX для рендеринга
- WinUI использует для рендеринга DirectX/DirectComposition
Так же, есть различие в используемых компонентах, библиотеках и подходах. Иными словами, все технологии, представленные в статье, имеют множество различий при схожей функциональности даже в рамках одной целевой платформы.
Часть 4. Ресурсы и скорость запуска
Сейчас мы подошли к той части «исследования», когда в дело вступают цифры. В данной части посмотрим на то, сколько приложения занимают места в оперативной памяти. Сравнивать будем в двух режимах: в Visual Studio в режиме Debug и в диспетчере задач запущенное опубликованное приложение (конфигурация Release).
WinForms | WPF | WinUI | .NET MAUI | Avalonia | Uno Platform | |
Debug (Mb) | 15 | 80 | 80 | 132 | 82 | 62 |
Release (Mb) | 6,1 | 19,6 | 26,2 | 54,4 | 32,6 | 13,6 |
Эти значения получены из Visual Studio при запуске отладки каждого приложения по отдельности.
А вот релизную сборку я решил проверить более тщательно. Кроме того, стояла задача проверить скорость запуска каждого приложения. Понятно, что все они с такой мизерной функциональностью не должны загружаться долго, однако различия в скорости есть.
Для сбора данных я написал скрипт для PowerShell, который запускает приложение, ждет, когда оно загрузится (станет отзывчивым), дополнительно ждет пару секунд на случай неполной загрузки и выключает приложение. И так 100 раз подряд для каждого приложения. Попутно скрипт собирает данные об используемом объеме памяти и вычисляет скорость запуска.
В процессе тестов, обратил внимание, что некоторые приложения занимают значительный объем в памяти. Поискал информацию, с чем это может быть связано и нашел вариант, где ответственность возлагалась на запаздывающую сборку мусора. Сомнительно, но ок — добавил в скрипт принудительный вызов GC.
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$apps = @(
"PATH_TO_TicTacToeUno.exe",
"PATH_TO_TicTacToeMAUIApp.exe",
"PATH_TO_TicTacToeWPF.exe",
"PATH_TO_TicTacToeAvalonia.Desktop.exe",
"PATH_TO_TicTacToeWinUI.exe",
"PATH_TO_TicTacToeWinForms.exe"
)
$results = @{}
foreach ($app in $apps) {
$appName = [System.IO.Path]::GetFileNameWithoutExtension($app)
$times = @()
$memoryUsages = @()
for ($i = 1; $i -le 100; $i++) {
Write-Host "Starting $appName (Iteration $i of 100)..."
$start = Get-Date
$process = Start-Process $app -PassThru
# Ждем, пока главное окно приложения не станет отзывчивым
while (-not $process.MainWindowHandle) {
Start-Sleep -Milliseconds 100
}
$end = Get-Date
$duration = ($end - $start).TotalSeconds
$times += $duration
# Измеряем использование памяти
Start-Sleep -Seconds 2 # Даем приложению время полностью загрузиться
$memory = (Get-Process -Id $process.Id).PrivateMemorySize64 / 1MB
$memoryUsages += $memory
# Закрываем приложение
Stop-Process $process.Id -Force
while (-not $process.HasExited) {
Start-Sleep -Milliseconds 100
}
# Освобождение ресурсов и сборка мусора
$process.Dispose()
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
# Ждем немного перед следующим запуском
Start-Sleep -Seconds 5
}
$averageTime = ($times | Measure-Object -Average).Average
$averageMemory = ($memoryUsages | Measure-Object -Average).Average
$results[$appName] = @{
Time = $averageTime
Memory = $averageMemory
}
}
# Вывод результатов
Write-Host "`nResults after 100 launches:"
$results.GetEnumerator() | Sort-Object {$_.Value.Time} | ForEach-Object {
Write-Host ("{0,-30} : StartUp Time: {1:N3} seconds, Memory Usage: {2:N2} Mb" -f $_.Key, $_.Value.Time, $_.Value.Memory)
}
Дополнительно отмечу, что скрипт я запускал по несколько раз с некоторыми изменениями, в частности брал данные о памяти как из process.WorkingSet64 так и process.PrivateMemorySize64, а так же для подстраховки продублировал тесты в Python-скрипте. Суммарно вышло что-то около 500-600 запусков каждого приложения, что в общем дает вполне понятную картину.
Еще, прежде чем показать результаты, оговорюсь относительно WinUI приложения — оно доставило больше всех хлопот, как ни странно, поскольку запускалось уверенно в VS и даже тесты проходили в уже готовом и установленном приложении, однако по факту не запускалось. Ошибка с Windows.ui.xaml.dll вызываемая непонятно по какой причине, не давала мне покоя несколько дней. Несколько тестов были проведены с этой “особенностью”. Тем не менее глобально, после того, как проблема была решена, ничего в цифрах не поменялось кардинально.
Итак, ниже скриншоты из PowerShell:
1 запуск:
![PowerShell - 1 запуск PowerShell - 1 запуск](https://habrastorage.org/getpro/habr/upload_files/a2a/477/366/a2a4773665b726cfb7e8dddb5acf9ba1.png)
5 запусков
PowerShell - 5 запусков 10 запусков:
![PowerShell - 10 запусков PowerShell - 10 запусков](https://habrastorage.org/getpro/habr/upload_files/e6f/549/877/e6f5498777bed04655fd2b11a2934ba6.png)
100 запусков:
![PowerShell - 100 запусков PowerShell - 100 запусков](https://habrastorage.org/getpro/habr/upload_files/9eb/6c3/0e4/9eb6c30e42de6001cf8808ba62fc8cc2.png)
10 запусков на Python:
![Python - 10 запусков Python - 10 запусков](https://habrastorage.org/getpro/habr/upload_files/700/ee7/446/700ee74461d45309eae97df7cee60fa4.png)
Результаты интересные. По скорости запуска различия есть, но по сути на уровне погрешности — все запускаются быстро. Причем, когда наблюдаешь вживую за процессом выполнения скрипта, заметно, что первые 3-5 раз загрузка UI происходит дольше, чем последующие. Связано это с кэшированием на уровне ОС. Правда актуально это для ClickOnce-сборок в большей степени, но заметно и на WInUI, которую запускал без установки.
Гораздо интереснее ситуация с памятью. Тут и явный лидер — неожиданно - Uno Platform, и явный аутсайдер - WPF.
Самый последний тест, при котором все уже работало и не падало - 100 итераций в PowerShell. Там же и минимальное значение задействованных ресурсов - в среднем 1,46 Мб. Тоже, скорее всего, связано с каким-то кэшированием, потому что в других тестах стабильно требовал ~ 8Мб.
Но всегда, во всех тестах, больше всего памяти отъедал WPF. Скорее всего это связано с тем, что учитываются “общие ресурсы” используемые другими приложениями в это же время в системе. По-крайней мере именно из-за этого вносил изменение в скрипт для получения данных из PrivateMemorySize64, однако тесты показали, что разницы нет. Возможна ли тут ошибка? Да, вполне может быть. Но и Python-скрипт показал такие же данные, поэтому я склоняюсь все же к тому, что WPF действительно самый ресурсоемкий фреймворк.
Но я предполагал, что им окажется MAUI. И по используемым ресурсам, MAUI лишь WPF уступил в объеме задействованной памяти. В это же время, скорость запуска во всех тестах у MAUI была самой низкой.
Часть 5. Сложность разработки
На самом деле, оценить сложность разработки в каждом конкретном фреймворке нельзя. Когда он, фреймворк, для тебя новый — сложно. Со временем становится сильно проще. Впрочем, как и в любом другом деле. Поэтому сказать однозначно, что в MAUI сложнее разрабатывать десктопное приложение, чем в WinUI, например, нельзя.
Но совершенно точно WinForms - самый простой из представленных фреймворков в освоении. Тому есть два объяснения — графический редактор (дизайнер) и отсутствие необходимости в использовании MVVM паттерна и бесконечного числа биндингов.
Но, опять же, все зависит от сложности проекта. В моем тестовом проекте везде было одинаково несложно. Даже с учетом того, что опыта с Uno и WinUI до последнего времени у меня не было.
Часть 6. Сборка и развертывание
Пару слов про то, насколько сложно подготовить проект к публикации и использовать его потом.
В целом, процесс должен быть не очень сложным везде. На практике, есть множество нюансов. В частности, неожиданно WinUI преподнес неприятные сюрпризы. Различных решений проблемы я нашел множество, но ни одно так и не помогло и, в конечном счете, запускал приложение для тестов из папки Release. Остальные приложения собрались и установились без проблем.
Дополнительно надо иметь в виду, что приложению требуется сертификат. То есть да, можно не подписывать приложение вовсе, но и распространять его будет сложнее - Windows на сторонних компьютерах будет ругаться при установке. Поэтому, хотя бы тестовым сертификатом подписать приложение стоит. А вот полноценный сертификат стоит денег, а в текущих условиях еще и надо найти, где его приобрести.
Конечно, это всё нюансы и наверно есть какие-то пути решения вопроса, да и вопрос сам не первоочередной важности. Но знать об этом стоит.
Часть 7. Итог
Что ж, стоит подвести итоги нашего небольшого исследования-сравнения. Для кого вообще оно делалось? Для тех, кто не занимался разработкой оконных приложений, но хотел бы понять, какие есть актуальные технологии. И вот эти технологии мы рассмотрели. Весьма поверхностно, конечно, но и задачи писать энциклопедию о том, как написать оконное приложение со всеми тонкостями, не стояло. А вот сделать вывод о том, с чего начать вкатываться в мир разработки приложений под Windows, а потом и других платформ, вполне можно.
Мое видение, основанное на имевшемся ранее и полученном сейчас такое:
- Начинать стоит все-таки с базы – с Windows Forms. Да, технология не нова, даже можно ее считать устаревшей. Но она живая и функциональная. Многие приемы на ней можно отработать, да и собрать действительно сложное приложение с графическим интерфейсом вполне реально. Да чего там – так делали, делали много и долго.
- Вторым шагом стоит изучить WPF. Эта технология позволит развить понимание паттерна MVVM, проектирование интерфейса в XAML и откроет путь к более современным стекам.
- Дальше путь открыт ко всему. Я был приятно удивлен возможностям Uno Platform, не ожидал. Кроме того, что можно написать приложение под любую актуальную ОС, в том числе и мобильную, так еще и весьма бережно эта технология относится, как оказалось, к ресурсам. Не то, чтобы это сильно критично, но, когда проект большой и нужно бороться за производительность, приложение, потребляющее меньше ресурсов будет выглядеть предпочтительнее на мой взгляд.
Еще из важного – возможность собрать дизайн приложения Uno в Figma и импортировать код интерфейса прямо в проект в виде XAML-кода! Такого нет ни у кого из других рассмотренных фреймворков.
И, напоследок, пару слов о размере приложения на диске.
![PowerShell - размер папок с установщиками PowerShell - размер папок с установщиками](https://habrastorage.org/getpro/habr/upload_files/c8f/cb8/9fe/c8fcb89fed9b9b0f01cf6c556355850d.png)
Здесь в папках располагаются установщики. WinForms - ожидаемо, самое “легкое” приложение. MAUI - без комментариев. Хотя нет, уточню, что в сборке MAUI отсутствуют билды под другие платформы, то есть это именно Windows-приложение.
На этом все. Делайте свои выводы, пробуйте разрабатывать оконные приложения и успехов!
P.S. исходный код всех проектов доступен по ссылке: https://github.com/algmironov/WinAppFrameworksComparison