Как реализовать Dependency Injection в Xamarin-приложении?

В Xamarin-приложениях внедрение зависимостей (Dependency Injection, DI) используется для управления зависимостями между компонентами, увеличения модульности, упрощения тестирования и поддержки принципов SOLID, особенно принципа инверсии зависимостей (Dependency Inversion Principle). Это означает, что вместо создания объектов внутри классов, зависимости внедряются извне, обычно через конструкторы или свойства, а сами зависимости управляются контейнером.

Xamarin.Forms позволяет реализовывать DI как вручную, так и с помощью популярных библиотек, таких как Microsoft.Extensions.DependencyInjection, Autofac, Unity, DryIoc, TinyIoC и других. В последних версиях Xamarin.Forms предпочтительно использовать Microsoft.Extensions.DependencyInjection, так как она интегрируется с .NET MAUI и едина с ASP.NET Core.

Базовые понятия Dependency Injection

  • Service (сервис) — объект, который выполняет определённую функцию (например, работа с API или локальным хранилищем).

  • Client (клиент) — объект, который использует сервис.

  • Container (контейнер) — система, управляющая созданием, хранением и внедрением зависимостей.

  • Registration (регистрация) — привязка интерфейса к реализации.

  • Lifetime (время жизни) — управление тем, как долго объект живёт (Singleton, Scoped, Transient).

Способы внедрения DI в Xamarin.Forms

1. Использование Microsoft.Extensions.DependencyInjection

Этот способ максимально приближен к стилю ASP.NET Core и .NET MAUI.

Настройка:

Шаг 1. Добавьте NuGet-пакет

Microsoft.Extensions.DependencyInjection

Шаг 2. Создайте интерфейс и реализацию

public interface IDataService
{
string GetData();
}
public class DataService : IDataService
{
public string GetData() => "Пример данных";
}

Шаг 3. Инициализация контейнера

В App.xaml.cs:

public static IServiceProvider ServiceProvider { get; private set; }
public App()
{
var services = new ServiceCollection();
ConfigureServices(services);
ServiceProvider = services.BuildServiceProvider();
InitializeComponent();
MainPage = new MainPage();
}
private void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IDataService, DataService>();
services.AddTransient<MainViewModel>();
}

Шаг 4. Внедрение в ViewModel через конструктор

public class MainViewModel
{
private readonly IDataService \_dataService;
public string Data => \_dataService.GetData();
public MainViewModel(IDataService dataService)
{
\_dataService = dataService;
}
}

Шаг 5. Получение ViewModel из DI в странице

BindingContext = App.ServiceProvider.GetRequiredService<MainViewModel>();

2. Использование сторонних контейнеров (например, Autofac)

Шаг 1. Установите NuGet-пакет

Autofac

Шаг 2. Регистрация

var builder = new ContainerBuilder();
builder.RegisterType<DataService>().As<IDataService>().SingleInstance();
builder.RegisterType<MainViewModel>();
var container = builder.Build();

Шаг 3. Использование

var vm = container.Resolve<MainViewModel>();

Жизненные циклы зависимостей

Lifetime Описание
Singleton Один и тот же экземпляр используется на протяжении всей жизни приложения
--- ---
Transient Каждый раз создается новый экземпляр
--- ---
Scoped Один и тот же экземпляр в пределах области (не применимо в Xamarin.Forms)
--- ---

Xamarin.Forms чаще всего использует Singleton и Transient.

Варианты внедрения зависимостей

1. Через конструктор

Самый предпочтительный способ, поддерживает тестируемость.

public MainViewModel(IDataService dataService) { }

2. Через свойства

Иногда используется в XAML, но хуже с точки зрения тестирования.

public IDataService DataService { get; set; }

3. Через ServiceLocator (антипаттерн)

var service = App.ServiceProvider.GetService<IDataService>();

Использовать с осторожностью, так как может затруднять модульное тестирование.

DI и MVVM

Dependency Injection тесно интегрируется с MVVM-архитектурой в Xamarin:

  • ViewModel не создает свои зависимости, а получает их извне.

  • View (страница) получает ViewModel из контейнера и задаёт BindingContext.

  • Позволяет внедрять сервисы INavigation, IApiClient, ILocalStorage, IAuthService, и т. д.

Пример с навигацией и сервисом

public interface IAuthService
{
Task<bool> LoginAsync(string user, string pass);
}
public class AuthService : IAuthService
{
public Task<bool> LoginAsync(string user, string pass)
{
return Task.FromResult(user == "admin" && pass == "1234");
}
}
ViewModel:
public class LoginViewModel
{
private readonly IAuthService \_authService;
public LoginViewModel(IAuthService authService)
{
\_authService = authService;
}
public async Task<bool> TryLogin(string user, string pass)
{
return await \_authService.LoginAsync(user, pass);
}
}

Регистрация:

services.AddSingleton<IAuthService, AuthService>();
services.AddTransient<LoginViewModel>();

Использование:

BindingContext = App.ServiceProvider.GetRequiredService<LoginViewModel>();

Преимущества внедрения зависимостей

  • Упрощение тестирования — легко заменить зависимости на моки.

  • Уменьшение связности — компоненты не знают конкретных реализаций.

  • Повышение модульности — сервисы и компоненты разрабатываются изолированно.

  • Гибкость — легко заменить реализацию без переписывания клиентского кода.

  • Поддержка принципов SOLID — особенно D, O и S.

DI в платформенно-зависимом коде (iOS/Android)

Если нужно внедрять зависимости в Custom Renderer, DependencyService, или платформенные сервисы, их можно резолвить из общедоступного контейнера:

var service = App.ServiceProvider.GetService<IMyService>();

Также можно использовать Dependency Injection через конструкторы при регистрации платформенных компонентов вручную.

DI и DependencyService: сравнение

Свойство Dependency Injection DependencyService
Тип внедрения Инверсный контроль Локатор сервисов
--- --- ---
Поддержка интерфейсов Да Да
--- --- ---
Тестируемость Высокая Средняя
--- --- ---
Платформенные реализации Через DI + платформенный регистр Через [Dependency] и Register
--- --- ---
Гибкость Высокая Ограниченная
--- --- ---
Совместимость с MVVM Отличная Ограниченная
--- --- ---

DependencyService подходит для простых задач, но полноценный DI предпочтительнее для масштабируемых приложений.

Популярные библиотеки DI

Библиотека Особенности
Microsoft.Extensions.DependencyInjection Поддерживается .NET Core и MAUI
--- ---
Autofac Мощный, гибкий, поддержка модулей и условных привязок
--- ---
DryIoc Высокая производительность, малый размер
--- ---
Unity От Microsoft, хорошая интеграция с MVVM Light
--- ---
TinyIoC Простая, легковесная
--- ---

Dependency Injection — ключевой подход при разработке архитектурно чистых Xamarin-приложений. Он повышает гибкость, удобство сопровождения, масштабируемость и позволяет легко тестировать отдельные компоненты приложения. С его помощью можно полностью контролировать зависимости между слоями приложения, управлять временем жизни объектов и обеспечивать единый стиль архитектуры как в кросс-платформенном коде, так и в платформенных реализациях.