Platform-Specific Code in .NET MAUI with Partial Classes

Cross-platform development is brilliant until you need to do something that only exists on one platform. Accessing the Android notification system, using iOS-specific haptics, or tapping into Windows registry values — these require platform-specific code. MAUI gives you several approaches, but partial classes with multi-targeting are the cleanest.

The Problem

You want to call platform-specific APIs from your shared code. In Xamarin.Forms, you'd typically use DependencyService or conditional compilation with #if. Both approaches have drawbacks: DependencyService adds runtime overhead and indirection, while #if blocks make your code a mess of preprocessor directives.

Partial Classes to the Rescue

MAUI's single-project structure, combined with C#'s partial keyword, gives you a far better option. Define a partial class in your shared code with the contract, then provide platform-specific implementations in the Platforms folders.

Start with the shared definition:

Services/DeviceOrientationService.cs
namespace MyApp.Services;

public partial class DeviceOrientationService
{
    public partial DeviceOrientation GetOrientation();
}

public enum DeviceOrientation
{
    Undefined,
    Landscape,
    Portrait
}

Now provide the Android implementation:

Platforms/Android/Services/DeviceOrientationService.cs
using Android.Content;
using Android.Views;
using Android.Runtime;

namespace MyApp.Services;

public partial class DeviceOrientationService
{
    public partial DeviceOrientation GetOrientation()
    {
        var context = Platform.CurrentActivity ?? Platform.AppContext;
        var windowManager = context.GetSystemService(Context.WindowService)
            .JavaCast<IWindowManager>();

        var rotation = windowManager.DefaultDisplay.Rotation;

        return rotation is SurfaceOrientation.Rotation90
            or SurfaceOrientation.Rotation270
            ? DeviceOrientation.Landscape
            : DeviceOrientation.Portrait;
    }
}

And the iOS implementation:

Platforms/iOS/Services/DeviceOrientationService.cs
using UIKit;

namespace MyApp.Services;

public partial class DeviceOrientationService
{
    public partial DeviceOrientation GetOrientation()
    {
        var orientation = UIDevice.CurrentDevice.Orientation;

        return orientation is UIDeviceOrientation.LandscapeLeft
            or UIDeviceOrientation.LandscapeRight
            ? DeviceOrientation.Landscape
            : DeviceOrientation.Portrait;
    }
}

How Multi-Targeting Makes This Work

The key is in the .csproj file. MAUI projects target multiple frameworks simultaneously:

config.xml
<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks>

When building for Android, MSBuild includes files from Platforms/Android/ and excludes other platform folders. The compiler sees the shared partial class plus only the relevant platform implementation. No preprocessor directives needed.

The default file inclusion rules handle this automatically, but you can customise them if needed:

config.xml
<!-- Default behaviour — already set by the MAUI SDK -->
<ItemGroup>
  <Compile Remove="Platforms\**\*.cs" />
  <Compile Include="Platforms\Android\**\*.cs"
           Condition="$(TargetFramework.Contains('android'))" />
  <Compile Include="Platforms\iOS\**\*.cs"
           Condition="$(TargetFramework.Contains('ios'))" />
</ItemGroup>

Registering with Dependency Injection

Because the partial class is a concrete type (not hidden behind an interface you need to resolve at runtime), registration is straightforward:

MauiProgram.cs
builder.Services.AddSingleton<DeviceOrientationService>();

If you prefer coding to an interface for testability, add one:

Example.cs
public interface IDeviceOrientationService
{
    DeviceOrientation GetOrientation();
}

// The partial class implements it
public partial class DeviceOrientationService : IDeviceOrientationService
{
    public partial DeviceOrientation GetOrientation();
}

// Register against the interface
builder.Services.AddSingleton<IDeviceOrientationService, DeviceOrientationService>();

When to Use Each Approach

Partial classes are best when you have a service or component with platform-specific logic that needs to be cleanly separated. They compile down to a single class with no runtime cost.

Conditional compilation (#if ANDROID) is fine for small, one-off checks in shared code:

Example.cs
public void ConfigurePadding()
{
#if IOS
    Padding = new Thickness(0, 20, 0, 0);
#elif ANDROID
    Padding = new Thickness(0);
#endif
}

But once you have more than two or three lines of platform-specific code, move it to a partial class.

DeviceInfo.Platform runtime checks are useful when behaviour differs slightly but uses the same APIs:

Example.cs
if (DeviceInfo.Platform == DevicePlatform.iOS)
    margin = 20;
else
    margin = 0;

A Real-World Example: Platform Notifications

Here's a more substantial example — a notification service:

Services/INotificationService.cs
public interface INotificationService
{
    Task ShowLocalNotification(string title, string body);
}

// Services/NotificationService.cs
public partial class NotificationService : INotificationService
{
    public partial Task ShowLocalNotification(string title, string body);
}

Each platform folder then contains the implementation using its native notification APIs. The shared code never knows or cares about the platform specifics — it just calls ShowLocalNotification and trusts that the right implementation is compiled in.

This pattern scales well. As your app grows, your Platforms/ folders naturally organise platform-specific code while your shared project remains clean and focused on business logic.