Что такое DependencyService и для чего он нужен?
DependencyService в Xamarin.Forms — это встроенный механизм внедрения зависимостей (Dependency Injection), который позволяет приложению на общем уровне (shared code) обращаться к платформенно-специфичным API Android, iOS и других целевых платформ, не нарушая принципов кроссплатформенной архитектуры.
Это ключевой инструмент, когда требуется использовать возможности конкретной платформы, которые не реализованы в самом Xamarin.Forms (например, доступ к файловой системе, сенсорам, Bluetooth, уведомлениям, журналам вызовов, фоновой работе и т.д.).
Основная идея DependencyService
-
На уровне общего кода (shared project) задаётся интерфейс.
-
В каждом платформенном проекте (iOS/Android/Windows) создаётся реализация интерфейса.
-
Эти реализации регистрируются и вызываются с помощью DependencyService.Get<T>().
Таким образом, достигается инверсия зависимостей: общий код зависит не от конкретной реализации, а от интерфейса, который затем разрешается во время выполнения.
1. Зачем нужен DependencyService
Xamarin.Forms предоставляет обёртки над стандартными элементами UI, но не покрывает все возможности платформ Android и iOS. С помощью DependencyService можно:
-
Доступ к датчику GPS, акселерометру, камере.
-
Взаимодействие с нативными API (например, Android NotificationManager).
-
Открытие файлов, работа с Bluetooth, NFC.
-
Доступ к уникальным идентификаторам устройства.
-
Настройка звука, фона, яркости.
-
Использование нативных библиотек сторонних производителей.
2. Основные компоненты DependencyService
-
Интерфейс, определённый в общем проекте.
-
Классы-реализации для каждой платформы (с аннотацией [assembly: Dependency]).
-
Вызов через DependencyService.Get<IService>() в общем коде.
3. Полный пример использования DependencyService
Шаг 1: Создание интерфейса в shared проекте
public interface IDeviceService
{
string GetDeviceName();
}
Шаг 2: Реализация интерфейса в Android проекте
using Android.OS;
using Xamarin.Forms;
using YourAppName.Droid;
\[assembly: Dependency(typeof(DeviceService_Android))\]
namespace YourAppName.Droid
{
public class DeviceService_Android : IDeviceService
{
public string GetDeviceName()
{
return Build.Model;
}
}
}
Шаг 3: Реализация интерфейса в iOS проекте
using UIKit;
using Xamarin.Forms;
using YourAppName.iOS;
\[assembly: Dependency(typeof(DeviceService_iOS))\]
namespace YourAppName.iOS
{
public class DeviceService_iOS : IDeviceService
{
public string GetDeviceName()
{
return UIDevice.CurrentDevice.Name;
}
}
}
Шаг 4: Вызов сервиса в общем коде (например, ViewModel или Page)
var deviceName = DependencyService.Get<IDeviceService>().GetDeviceName();
await DisplayAlert("Устройство", deviceName, "OK");
4. Особенности реализации
-
Классы реализации должны быть public и иметь конструктор без параметров.
-
[assembly: Dependency(typeof(...))] обязателен для регистрации.
-
Вызов DependencyService.Get<T>() возвращает null, если реализация не найдена.
5. Альтернативы DependencyService
Хотя DependencyService является встроенным и простым решением, он имеет ограничения:
-
Работает через рефлексию, что снижает производительность.
-
Не поддерживает внедрение зависимостей с параметрами.
-
Не поддерживает жизненные циклы объектов (singleton, transient и т.д.).
-
Трудно покрывать юнит-тестами.
Поэтому в более сложных проектах используют сторонние DI-фреймворки:
-
Microsoft.Extensions.DependencyInjection (официальная библиотека .NET).
-
**Autofac
** - **Unity
** - **DryIoc
**
6. Использование в асинхронных сценариях
DependencyService работает только с синхронными интерфейсами. Он не поддерживает внедрение интерфейсов с async-методами напрямую. Чтобы обойти это ограничение, методы возвращают Task, но вызов через DependencyService остаётся синхронным:
public interface IFileService
{
Task<string> ReadFileAsync(string filename);
}
В платформенной реализации:
public async Task<string> ReadFileAsync(string filename)
{
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), filename);
return await File.ReadAllTextAsync(path);
}
7. Пример: Вибрация устройства
Интерфейс:
public interface IVibrationService
{
void Vibrate();
}
Android-реализация:
using Android.Content;
using Android.OS;
using Xamarin.Forms;
using YourAppName.Droid;
\[assembly: Dependency(typeof(VibrationService))\]
namespace YourAppName.Droid
{
public class VibrationService : IVibrationService
{
public void Vibrate()
{
var vibrator = (Vibrator)Android.App.Application.Context.GetSystemService(Context.VibratorService);
vibrator.Vibrate(VibrationEffect.CreateOneShot(500, VibrationEffect.DefaultAmplitude));
}
}
}
iOS-реализация:
using AudioToolbox;
using Xamarin.Forms;
using YourAppName.iOS;
\[assembly: Dependency(typeof(VibrationService))\]
namespace YourAppName.iOS
{
public class VibrationService : IVibrationService
{
public void Vibrate()
{
SystemSound.Vibrate.PlaySystemSound();
}
}
}
Вызов в общем коде:
DependencyService.Get<IVibrationService>()?.Vibrate();
8. Работа с несколькими реализациями одного интерфейса
DependencyService поддерживает только одну реализацию интерфейса на платформу. Если требуется больше гибкости, нужно использовать вручную регистрацию или DI-контейнеры.
9. Совместимость с .NET MAUI
В Xamarin.Forms, DependencyService — основной механизм внедрения зависимостей. Однако в .NET MAUI (его преемнике) рекомендован встроенный сервисный контейнер (Microsoft.Extensions.DependencyInjection), который более мощный, типизированный и гибкий. Тем не менее, подход через DependencyService продолжает работать и может использоваться при переносе проектов.
10. Советы по использованию
-
Интерфейсы лучше держать в отдельных папках (Interfaces или Services).
-
Не используйте DependencyService для бизнес-логики — только для платформенной.
-
Оборачивайте вызовы DependencyService.Get<T>() в отдельные классы или property-инъекции — это облегчает тестирование и переход на полноценный DI в будущем.
-
Проверяйте null, особенно если реализация интерфейса не найдена (на одной из платформ).
DependencyService — это простой и эффективный способ интеграции платформенно-специфичных возможностей в Xamarin-приложении при сохранении кроссплатформенной архитектуры. Он даёт разработчику доступ к мощным API Android и iOS из общего кода, используя чистые абстракции и минимальный шаблон кода.