Как реализовать 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-приложений. Он повышает гибкость, удобство сопровождения, масштабируемость и позволяет легко тестировать отдельные компоненты приложения. С его помощью можно полностью контролировать зависимости между слоями приложения, управлять временем жизни объектов и обеспечивать единый стиль архитектуры как в кросс-платформенном коде, так и в платформенных реализациях.