Skip to content

Latest commit

 

History

History
143 lines (97 loc) · 6.24 KB

platform-specific-csharp.md

File metadata and controls

143 lines (97 loc) · 6.24 KB
uid
Uno.Development.PlatformSpecificCSharp

Platform-specific C# code in Uno

Uno allows you to reuse views and business logic across platforms. Sometimes though you may want to write different code per platform. You may need to access platform-specific native APIs and 3rd-party libraries, or want your app to look and behave differently depending on the platform.

This guide covers multiple approaches to managing per-platform code in C#. See this guide for managing per-platform XAML.

Project structure

There are two ways to restrict code or XAML markup to be used only on a specific platform:

  • Use conditionals in a source file
  • Place the code in a separate file which is only included in the desired platform.

The structure of an Uno app created with the default Visual Studio template is explained in more detail here.

#if conditionals

The most basic means of authoring platform-specific code is to use #if conditionals:

#if HAS_UNO
Console.WriteLine("Uno Platform - Pixel-perfect WinUI apps that run everywhere");
#else
Console.WriteLine("Windows - Built with Microsoft's own tooling");
#endif

If the supplied condition is not met, e.g. if HAS_UNO is not defined, then the enclosed code will be ignored by the compiler.

The following conditional symbols are predefined for each Uno platform:

Platform Symbol Remarks
Android __ANDROID__
iOS __IOS__
Catalyst __MACCATALYST__
macOS __MACOS__
WebAssembly HAS_UNO_WASM Only available in the MyApp.WebAssembly head, see below
Skia HAS_UNO_SKIA
Non-Windows HAS_UNO To learn about symbols available when HAS_UNO is not present, see below

Tip

Conditionals can be combined with boolean operators, e.g. #if __ANDROID__ || __IOS__. It is also possible to define custom conditional compilation symbols per project in the 'Build' tab in the project's properties.

Windows-specific code

On Windows (the Windows head project), an Uno Platform application isn't using Uno.UI at all. It's compiled just like a single-platform desktop application, using Microsoft's own tooling. For that reason, the HAS_UNO symbol is not defined on Windows. This aspect can optionally be leveraged to write code specifically intended for Uno.

Apps generated with the default unoapp solution template use Windows App SDK when targeting Windows. While this is the recommended path for new Windows apps, some solutions instead use UWP to target Windows. Both app models define a different conditional symbol:

App model Symbol Remarks
Windows App SDK WINDOWS10_0_18362_0_OR_GREATER Depending on the TargetFramework value, the 18362 part may need adjustment
Universal Windows Platform NETFX_CORE No longer defined in new apps by default

WebAssembly considerations

The Uno Platform templates use a separate project library to share code between platforms. As of .NET 7, WebAssembly does not have its own TargetFramework, and Uno Platform uses the same value (e.g. net7.0) for both WebAssembly and Skia-based platforms. This means that __WASM__ and HAS_UNO_WASM are not available in this project, but are available in C# code specified directly in the MyApp.WebAssembly head.

In order to execute platform-specific code for WebAssembly, a runtime check needs to be included:

if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER")))
{
   // Do something WebAssembly specific
}

Note

JSImport/JSExport are available on all platforms targeting .NET 7 and later, and this code does not need to be conditionally excluded.

WebAssembly is currently a net7.0 target, and cannot yet be discriminated at compile time until the inclusion of net8.0-browser in .NET 8. See the proposed design at dotnet/designs#289.

Type aliases

Defining a type alias with the using directive, in combination with #if conditionals, can make for cleaner code. For example:

#if __ANDROID__
using _View = Android.Views.View;
#elif __IOS__
using _View = UIKit.UIView;
#else
using _View = Windows.UI.Xaml.UIElement;
#endif

...

public IEnumerable<_View> FindDescendants(FrameworkElement parent) => ...

Partial class definitions

Heavy usage of #if conditionals in shared code makes it hard to read and comprehend. A better approach is to use partial class definitions to split shared and platform-specific code. These partial classes need to exist in the shared project as it's compiled separately from each project head. This method still requires #if conditionals.

A simple example

Shared code in PROJECTNAME/NativeWrapperControl.cs:

public partial class NativeWrapperControl : Control {

...

		protected override void OnApplyTemplate()
		{
			 base.OnApplyTemplate();
   
  			 _nativeView = CreateNativeView();
		}

Platform-specific code in PROJECTNAME/NativeWrapperControl.Android.cs:

#if __ANDROID__
public partial class NativeWrapperControl : Control {

...

		private View CreateNativeView() {
			... //Android-specific code
		}

Platform-specific code in PROJECTNAME/NativeWrapperControl.iOS.cs:

#if __IOS__
public partial class NativeWrapperControl : Control {

...

		private UIView CreateNativeView() {
			... //iOS-specific code
		}

You can use partial methods when only one platform needs specialized logic.