Универсальные подклассы 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 из немного медленнее, чем вызов элементов из не универсальных классов.