Работа с собственными типами в кроссплатформенных приложениях

В этой статье рассматриваются новые типы собственных API iOS (nint, nuint, nfloat) в кроссплатформенных приложениях, где код используется для устройств, отличных от iOS, таких как Android или Windows Телефон OSes.

64-типы собственных типов работают с API iOS и Mac. Если вы также пишете общий код, работающий в Android или Windows, вам потребуется управлять преобразованием унифицированных типов в обычные типы .NET, которые можно совместно использовать.

В этом документе рассматриваются различные способы взаимодействия с унифицированным API из общего и общего кода.

Когда следует использовать собственные типы

Интерфейсы API Xamarin.iOS и Xamarin.Mac по-прежнему включают intuint типы данных, float а также RectangleFSizeF типы и PointF типы. Эти существующие типы данных должны продолжать использоваться в любом общем кроссплатформенной коде. Новые типы данных native должны использоваться только при вызове API Mac или iOS, где требуется поддержка типов, поддерживающих архитектуру.

В зависимости от характера общего кода может возникнуть время, когда кроссплатформенный код может иметь дело с nintnuint типами данных.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 в (зная, что преобразование из nfloatfloat нее является преобразованием потери) и вызовите исходную версию подпрограммы для выполнения фактической работы. Например:

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, включив следующий код в верхнюю часть общих файлов исходного кода и преобразовав все необходимые uintintзначения или: nuintnintnfloatfloat

#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 результат вычисления (так как оба значения умножаются являются типомnfloatfloat) в возвращаемое значение.

Если код компилируется и выполняется на устройстве, отличном от единого 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.