Работа с собственными типами в кроссплатформенных приложениях
В этой статье рассматриваются новые типы собственных API iOS (nint, nuint, nfloat) в кроссплатформенных приложениях, где код используется для устройств, отличных от iOS, таких как Android или Windows Телефон OSes.
64-типы собственных типов работают с API iOS и Mac. Если вы также пишете общий код, работающий в Android или Windows, вам потребуется управлять преобразованием унифицированных типов в обычные типы .NET, которые можно совместно использовать.
В этом документе рассматриваются различные способы взаимодействия с унифицированным API из общего и общего кода.
Когда следует использовать собственные типы
Интерфейсы API Xamarin.iOS и Xamarin.Mac по-прежнему включают int
uint
типы данных, float
а также RectangleF
SizeF
типы и PointF
типы. Эти существующие типы данных должны продолжать использоваться в любом общем кроссплатформенной коде. Новые типы данных native должны использоваться только при вызове API Mac или iOS, где требуется поддержка типов, поддерживающих архитектуру.
В зависимости от характера общего кода может возникнуть время, когда кроссплатформенный код может иметь дело с nint
nuint
типами данных.nfloat
Например: библиотека, обрабатывающая преобразования для прямоугольных данных, которые ранее использовались System.Drawing.RectangleF
для совместного использования функций между версиями Xamarin.iOS и Xamarin.Android приложения, потребуется обновить для обработки собственных типов в iOS.
Способ обработки этих изменений зависит от размера и сложности приложения и формы совместного использования кода, как показано в следующих разделах.
Рекомендации по совместному использованию кода
Как указано в документе "Параметры кода общего доступа", существует два основных способа совместного использования кода между кроссплатформенными проектами: общими проектами и переносимыми библиотеками классов. Какой из двух типов использовался, ограничивает параметры, которые мы имеем при обработке собственных типов данных в кроссплатформенной коде.
Проекты переносимой библиотеки классов
Переносимая библиотека классов (PCL) позволяет нацеливать на поддерживаемые платформы и использовать интерфейсы для предоставления функциональных возможностей для конкретной платформы.
Так как тип проекта PCL компилируется до нее .DLL
, и он не имеет смысла единого API, вы будете вынуждены использовать существующие типы данных (int
, uint
, float
) в исходном коде PCL и тип приведения вызовов к классам и методам PCL в интерфейсных приложениях. Например:
using NativePCL;
...
CGRect rect = new CGRect (0, 0, 200, 200);
Console.WriteLine ("Rectangle Area: {0}", Transformations.CalculateArea ((RectangleF)rect));
Общие проекты
Тип проекта общего ресурса позволяет упорядочить исходный код в отдельном проекте, который затем включается и компилируется в отдельные интерфейсные приложения для конкретной платформы, а также использовать #if
директивы компилятора, необходимые для управления требованиями конкретной платформы.
Размер и сложность внешних мобильных приложений, использующих общий код, а также размер и сложность совместно используемого кода, необходимо учитывать при выборе метода поддержки собственных типов данных в кроссплатформенного проекта общего ресурса.
На основе этих факторов следующие типы решений могут быть реализованы с помощью if __UNIFIED__ ... #endif
директив компилятора для обработки конкретных изменений в коде унифицированного API.
Использование повторяющихся методов
Рассмотрим пример библиотеки, выполняющей преобразования для прямоугольных данных, приведенных выше. Если библиотека содержит только один или два очень простых метода, можно создать повторяющиеся версии этих методов для Xamarin.iOS и Xamarin.Android. Например:
using System;
using System.Drawing;
#if __UNIFIED__
using CoreGraphics;
#endif
namespace NativeShared
{
public class Transformations
{
#region Constructors
public Transformations ()
{
}
#endregion
#region Public Methods
#if __UNIFIED__
public static nfloat CalculateArea(CGRect rect) {
// Calculate area...
return (rect.Width * rect.Height);
}
#else
public static float CalculateArea(RectangleF rect) {
// Calculate area...
return (rect.Width * rect.Height);
}
#endif
#endregion
}
}
В приведенном выше коде, так как CalculateArea
подпрограмма очень проста, мы использовали условную компиляцию и создали отдельную версию единого API метода. С другой стороны, если библиотека содержала множество подпрограмм или несколько сложных подпрограмм, это решение не будет возможным, так как это приведет к проблеме, сохраняющей все методы в синхронизации для изменений или исправлений ошибок.
Использование перегрузки методов
В этом случае решение может быть создано для создания перегруженной версии методов с использованием 32-разрядных типов данных, чтобы они теперь принимают CGRect
в качестве параметра и (или) возвращаемое значение, преобразуйте это значение RectangleF
в (зная, что преобразование из nfloat
float
нее является преобразованием потери) и вызовите исходную версию подпрограммы для выполнения фактической работы. Например:
using System;
using System.Drawing;
#if __UNIFIED__
using CoreGraphics;
#endif
namespace NativeShared
{
public class Transformations
{
#region Constructors
public Transformations ()
{
}
#endregion
#region Public Methods
#if __UNIFIED__
public static nfloat CalculateArea(CGRect rect) {
// Call original routine to calculate area
return (nfloat)CalculateArea((RectangleF)rect);
}
#endif
public static float CalculateArea(RectangleF rect) {
// Calculate area...
return (rect.Width * rect.Height);
}
#endregion
}
}
Опять же, это хорошее решение, если потеря точности не влияет на результаты для конкретных потребностей вашего приложения.
Использование директив псевдонима
Для областей, в которых потеря точности является проблемой, можно использовать using
директивы для создания псевдонима для типов данных Native и CoreGraphics, включив следующий код в верхнюю часть общих файлов исходного кода и преобразовав все необходимые uint
int
значения или: nuint
nint
nfloat
float
#if __UNIFIED__
// Mappings Unified CoreGraphic classes to MonoTouch classes
using RectangleF = global::CoreGraphics.CGRect;
using SizeF = global::CoreGraphics.CGSize;
using PointF = global::CoreGraphics.CGPoint;
#else
// Mappings Unified types to MonoTouch types
using nfloat = global::System.Single;
using nint = global::System.Int32;
using nuint = global::System.UInt32;
#endif
Таким образом, наш пример кода становится следующим:
using System;
using System.Drawing;
#if __UNIFIED__
// Map Unified CoreGraphic classes to MonoTouch classes
using RectangleF = global::CoreGraphics.CGRect;
using SizeF = global::CoreGraphics.CGSize;
using PointF = global::CoreGraphics.CGPoint;
#else
// Map Unified types to MonoTouch types
using nfloat = global::System.Single;
using nint = global::System.Int32;
using nuint = global::System.UInt32;
#endif
namespace NativeShared
{
public class Transformations
{
#region Constructors
public Transformations ()
{
}
#endregion
#region Public Methods
public static nfloat CalculateArea(RectangleF rect) {
// Calculate area...
return (rect.Width * rect.Height);
}
#endregion
}
}
Обратите внимание, что здесь мы изменили CalculateArea
метод для возврата nfloat
вместо стандартного float
. Это было сделано так, чтобы мы не получили ошибку компиляции, пытаясь неявно преобразовать nfloat
результат вычисления (так как оба значения умножаются являются типомnfloat
float
) в возвращаемое значение.
Если код компилируется и выполняется на устройстве, отличном от единого API, using nfloat = global::System.Single;
сопоставляется nfloat
с Single
неявным преобразованием float
в приложение, использующее интерфейсное приложение, вызывать CalculateArea
метод без изменений.
Использование преобразований типов в интерфейсном приложении
В случае, если интерфейсные приложения выполняют только несколько вызовов к общей библиотеке кода, другое решение может оставить библиотеку без изменений и выполнить приведение типов в приложении Xamarin.iOS или Xamarin.Mac при вызове существующей подпрограммы. Например:
using NativeShared;
...
CGRect rect = new CGRect (0, 0, 200, 200);
Console.WriteLine ("Rectangle Area: {0}", Transformations.CalculateArea ((RectangleF)rect));
Если используемое приложение делает сотни вызовов к общей библиотеке кода, это может оказаться не хорошим решением.
На основе архитектуры приложения мы можем в конечном итоге использовать одно или несколько описанных выше решений для поддержки собственных типов данных (где это необходимо) в кроссплатформенный код.
Приложения Xamarin.Forms
Для кроссплатформенных интерфейсов UIs необходимо использовать Xamarin.Forms, которые также будут совместно использоваться приложением Унифицированного API:
- Все решение должно использовать пакет NuGet версии 1.3.1 (или более поздней) пакета NuGet Xamarin.Forms.
- Для любых пользовательских отрисовок Xamarin.iOS используйте те же типы решений, представленных выше, в зависимости от того, как был предоставлен общий доступ к коду пользовательского интерфейса (общий проект или PCL).
Как и в стандартном кроссплатформенное приложение, существующие 32-разрядные типы данных должны использоваться в любом общем кроссплатформенного кода для большинства ситуаций. Новые собственные типы данных следует использовать только при вызове API Mac или iOS, где требуется поддержка типов, поддерживающих архитектуру.
Дополнительные сведения см. в документации по обновлению существующих приложений Xamarin.Forms.
Итоги
В этой статье мы видели, когда использовать собственные типы данных в приложении единого API и их последствия кроссплатформенных. Мы представили несколько решений, которые можно использовать в ситуациях, когда новые собственные типы данных должны использоваться в кроссплатформенных библиотеках. Кроме того, мы ознакомились с кратким руководством по поддержке унифицированных API в кроссплатформенных приложениях Xamarin.Forms.