Универсальные подклассы NSObject в Xamarin.iOS

Использование универсальных шаблонов совместно с NSObject

Универсальные шаблоны можно использовать в подклассах NSObject, например UIView:

class Foo<T> : UIView {
    public Foo (CGRect x) : base (x) {}
    public override void Draw (CoreGraphics.CGRect rect)
    {
        Console.WriteLine ("T: {0}. Type: {1}", typeof (T), GetType ().Name);
    }
}

Поскольку объекты в подклассе NSObject зарегистрированы в среде выполнения Objective-C, существуют некоторые ограничения возможностей универсальных подклассов типов NSObject.

Рекомендации для универсальных подклассов NSObject

В этом документе описаны ограничения в ограниченной поддержке универсальных подклассов NSObjects.

Аргументы универсального типа в сигнатурах элементов

Все аргументы универсального типа в сигнатуре элементов, доступных для Objective-C, должны иметь ограничение NSObject.

Хорошо:

class Generic<T> : NSObject where T: NSObject
{
    [Export ("myMethod:")]
    public void MyMethod (T value)
    {
    }
}

Причина. Параметр универсального типа — это NSObject, поэтому сигнатура селектора для myMethod: может быть безопасно предоставлена Objective-C (это всегда будет NSObject или его подкласс).

Плохо:

class Generic<T> : NSObject
{
    [Export ("myMethod:")]
    public void MyMethod (T value)
    {
    }
}

Причина: невозможно создать Objective-C подпись для экспортированных элементов, которые Objective-C могут вызывать код, так как подпись будет отличаться в зависимости от точного типа универсального типа T.

Хорошо:

class Generic<T> : NSObject
{
    T storage;

    [Export ("myMethod:")]
    public void MyMethod (NSObject value)
    {
    }
}

Причина: возможны неограниченные аргументы универсального типа, если они не участвуют в сигнатуре экспортированного элемента.

Хорошо:

class Generic<T, U> : NSObject where T: NSObject
{
    [Export ("myMethod:")]
    public void MyMethod (T value)
    {
        Console.WriteLine (typeof (U));
    }
}

Причина. Параметр T в экспортированном элементе Objective-CMyMethod ограничен до NSObject, неограниченный тип U не является частью сигнатуры.

Плохо:

class Generic<T> : NSObject
{
    public T Storage { get; }

    public Generic(T value)
    {
        Storage = value;
    }
}

[Register("Foo")]
class Foo: NSView
{
    [Export("Test")]
    public Generic<int> Test { get; set; } = new Generic<int>(22);

    [Export("Test1")]
    public Generic<object> Test1 { get; set; } = new Generic<object>(new object());

    [Export("Test2")]
    public Generic<NSObject> Test2 { get; set; } = new Generic<NSObject>(new NSObject());

}

Причина: пока registrar этот сценарий не поддерживается. Дополнительные сведения см. в этой статье GitHub.

Создание экземпляров универсальных типов из Objective-C

Создание экземпляров универсальных типов из Objective-C не допускается. Обычно это происходит, когда управляемый тип используется в xib или storyboard.

Рассмотрим это определение класса, которое предоставляет конструктор, принимающий IntPtr (способ создания объекта C# в Xamarin.iOS из собственного экземпляра Objective-C):

class Generic<T> : NSObject where T : NSObject
{
    public Generic () {}
    public Generic (IntPtr ptr) : base (ptr) {}
}

Хотя приведенная выше конструкция хороша, во время выполнения, когда Objective-C попытается создать его экземпляр, возникнет исключение.

Это происходит потому, что Objective-C не имеет концепции универсальных типов и не может указывать точный универсальный тип для создания.

Эту проблему можно устранить, создав специализированный подкласс универсального типа. Например:

class Generic<T> : NSObject where T : NSObject
{
    public Generic () {}
    public Generic (IntPtr ptr) : base (ptr) {}
}

class GenericUIView : Generic<UIView>
{
}

В таком случае неоднозначности больше не будет, а класс GenericUIView можно использовать в xib или storyboard.

Поддержки универсальных методов нет

Универсальные методы недопустимы.

Этот код не будет компилироваться:

class MyClass : NSObject
{
    [Export ("myMethod")]
    public void MyMethod<T> (T argument)
    {
    }
}

Причина. Он недопустим, поскольку Xamarin.iOS не знает, какой тип использовать для аргумента типа T при вызове метода из Objective-C.

Решением здесь является создание специализированного метода и его экспорт:

class MyClass : NSObject
{
    [Export ("myMethod")]
    public void MyUIViewMethod (UIView argument)
    {
        MyMethod<UIView> (argument);
    }
    public void MyMethod<T> (T argument)
    {
    }
}

Экспортированные статические элементы недопустимы

Статический элемент невозможно передать в Objective-C, если он содержится в универсальном подклассе NSObject.

Пример неподдерживаемого сценария:

class Generic<T> : NSObject where T : NSObject
{
    [Export ("myMethod:")]
    public static void MyMethod ()
    {
    }

    [Export ("myProperty")]
    public static T MyProperty { get; set; }
}

Причина. Как и универсальные методы, среда выполнения Xamarin.iOS должна иметь возможность узнать, какой тип использовать для аргумента универсального типа T.

Для элементов экземпляра используется сам экземпляр (поскольку это никогда не будет экземпляр Generic<T>, это всегда будет Generic<SomeSpecificClass>), но для статических элементов эти сведения отсутствуют.

Обратите внимание, что это справедливо, даже если рассматриваемый элемент не использует аргумент типа T каким бы то ни было способом.

Альтернативой в этом случае является создание специализированного подкласса:

class GenericUIView : Generic<UIView>
{
    [Export ("myUIViewMethod")]
    public static void MyUIViewMethod ()
    {
        MyMethod ();
    }

    [Export ("myProperty")]
    public static UIView MyUIViewProperty {
        get { return MyProperty; }
        set { MyProperty = value; }
    }
}

class Generic<T> : NSObject where T : NSObject
{
    public static void MyMethod () {}
    public static T MyProperty { get; set; }
}

Производительность

Статический registrar не может разрешить экспортируемый элемент в универсальном типе во время сборки, как он обычно делает. Его нужно смотреть во время выполнения. Это означает, что вызов такого метода Objective-C из немного медленнее, чем вызов элементов из не универсальных классов.