Интеграция WebAssembly и C#: создаем высокопроизводительные веб-приложения

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в WebAssembly и C#

WebAssembly (WASM) — это низкоуровневый бинарный формат инструкций, который позволяет запускать код, написанный на различных языках программирования, в веб-браузере с производительностью, близкой к нативной. Благодаря поддержке .NET и C#, разработчики могут переносить сложные вычислительные задачи, библиотеки и даже целые приложения на сторону клиента, используя привычный синтаксис и экосистему C#.

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

Как C# компилируется в WebAssembly

Процесс компиляции C# в WebAssembly основан на использовании Mono или .NET runtime, которые адаптированы для работы в среде WASM. Основной инструмент для этого — Blazor WebAssembly, который является частью платформы .NET.

Когда вы создаете Blazor WebAssembly приложение, компилятор преобразует ваш C# код в промежуточный язык (IL), а затем .NET runtime, скомпилированный в WebAssembly, загружается в браузер вместе с вашим приложением. Весь IL-код исполняется этим runtime-ом непосредственно в браузере.

Преимущества такого подхода:

  • Производительность — WebAssembly работает почти со скоростью нативного кода, что особенно важно для ресурсоемких задач (обработка изображений, шифрование, математические расчеты).
  • Безопасность — код выполняется в изолированной среде браузера.
  • Переносимость — один и тот же C# код может работать на сервере и на клиенте.

Практический пример: создание Blazor WebAssembly приложения

Рассмотрим создание простого приложения на Blazor WebAssembly, которое вычисляет числа Фибоначчи с использованием WebAssembly для повышения производительности.

Шаг 1: Установка инструментов

Убедитесь, что у вас установлен .NET SDK (версия 6.0 или выше). Для создания проекта используйте команду:

dotnet new blazorwasm -n FibonacciApp
cd FibonacciApp

Шаг 2: Создание сервиса для вычислений

Создайте файл Services/FibonacciService.cs:

using System;
using System.Collections.Generic;

namespace FibonacciApp.Services
{
    public class FibonacciService
    {
        public List<long> GenerateSequence(int count)
        {
            var sequence = new List<long>();
            long a = 0, b = 1;
            for (int i = 0; i < count; i++)
            {
                sequence.Add(a);
                long temp = a;
                a = b;
                b = temp + b;
            }
            return sequence;
        }

        public long CalculateNth(int n)
        {
            if (n <= 1) return n;
            long a = 0, b = 1;
            for (int i = 2; i <= n; i++)
            {
                long temp = a + b;
                a = b;
                b = temp;
            }
            return b;
        }
    }
}

Шаг 3: Регистрация сервиса в DI

В файле Program.cs добавьте регистрацию сервиса:

using FibonacciApp.Services;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<FibonacciService>();

await builder.Build().RunAsync();

Шаг 4: Использование в компоненте

Создайте компонент Pages/Fibonacci.razor:

@page "/fibonacci"
@inject FibonacciService FibonacciService

<h3>Вычисление чисел Фибоначчи</h3>

<div class="form-group">
    <label for="count">Количество чисел:</label>
    <input type="number" id="count" @bind="count" class="form-control" />
</div>

<button class="btn btn-primary" @onclick="Calculate">Вычислить</button>

@if (result != null)
{
    <h4>Результат:</h4>
    <ul>
        @foreach (var num in result)
        {
            <li>@num</li>
        }
    </ul>
}

@code {
    private int count = 10;
    private List<long>? result;

    private void Calculate()
    {
        result = FibonacciService.GenerateSequence(count);
    }
}

Теперь при запуске приложения (команда dotnet run) вы сможете перейти по адресу /fibonacci и увидеть вычисления, выполняемые на стороне клиента через WebAssembly.

Оптимизация производительности с помощью P/Invoke и WebAssembly

Для максимальной производительности Blazor WebAssembly использует механизм P/Invoke (Platform Invocation Services) для вызова нативных функций из WebAssembly. Это позволяет напрямую взаимодействовать с JavaScript API и вызывать функции из библиотек, написанных на C/C++.

Пример вызова JavaScript функции из C#:

using Microsoft.JSInterop;

public class JsInteropExample
{
    private readonly IJSRuntime _jsRuntime;

    public JsInteropExample(IJSRuntime jsRuntime)
    {
        _jsRuntime = jsRuntime;
    }

    public async Task<string> GetBrowserInfo()
    {
        return await _jsRuntime.InvokeAsync<string>("getBrowserInfo");
    }
}

А в JavaScript файле (например, wwwroot/js/interop.js):

window.getBrowserInfo = function() {
    return navigator.userAgent;
};

Этот подход особенно полезен, когда нужно интегрировать существующие JavaScript библиотеки или получить доступ к аппаратным возможностям браузера.

Ограничения и лучшие практики

Несмотря на мощь WebAssembly, существуют некоторые ограничения:

  • Размер загружаемого файла — .NET runtime занимает несколько мегабайт, что может увеличить время загрузки страницы. Рекомендуется использовать AOT компиляцию (Ahead-of-Time) для уменьшения размера и ускорения запуска.
  • Отсутствие прямого доступа к DOM — Blazor абстраги

Блоги

Рекомендаций по книгам